diff --git a/.circleci/config.yml b/.circleci/config.yml index 22539912268..dcf2ba804c6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,7 +7,7 @@ aliases: - &environment docker: # specify the version you desire here - - image: cimg/node:16.20-browsers + - image: cimg/node:20.14.0-browsers resource_class: xlarge # Specify service dependencies here if necessary # CircleCI maintains a library of pre-built images @@ -18,8 +18,6 @@ aliases: - &restore_dep_cache keys: - v1-dependencies-{{ checksum "package.json" }} - # fallback to using the latest cache if no exact match is found - - v1-dependencies- - &save_dep_cache paths: diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 69e13850258..9b1bb6e39cf 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,4 +1,4 @@ -ARG VARIANT="12" +ARG VARIANT="20" FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:${VARIANT} RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | gpg --dearmor > /usr/share/keyrings/yarn-archive-keyring.gpg diff --git a/.eslintrc.js b/.eslintrc.js index f17c7a0063d..5b69afa019f 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -61,6 +61,7 @@ module.exports = { 'no-useless-escape': 'off', 'no-console': 'error', 'jsdoc/check-types': 'off', + 'jsdoc/no-defaults': 'off', 'jsdoc/newline-after-description': 'off', 'jsdoc/require-jsdoc': 'off', 'jsdoc/require-param': 'off', @@ -89,11 +90,48 @@ module.exports = { name: 'require', message: 'use import instead' } + ], + 'prebid/no-global': [ + 'error', + ...['localStorage', 'sessionStorage'].map(name => ({name, message: 'use storageManager instead'})), + { + name: 'XMLHttpRequest', + message: 'use ajax.js instead' + }, + ], + 'prebid/no-member': [ + 'error', + { + name: 'cookie', + target: 'document', + message: 'use storageManager instead' + }, + { + name: 'sendBeacon', + target: 'navigator', + message: 'use ajax.js instead' + }, + ...['outerText', 'innerText'].map(name => ({ + name, + message: 'use .textContent instead' + })) ] } })).concat([{ // code in other packages (such as plugins/eslint) is not "seen" by babel and its parser will complain. files: 'plugins/*/**/*.js', parser: 'esprima' + }, { + files: '**BidAdapter.js', + rules: { + 'no-restricted-imports': [ + 'error', { + patterns: [ + '**/src/events.js', + '**/src/adloader.js' + ] + } + ] + } }]) }; diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b225be162a8..367ace94d37 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -41,7 +41,7 @@ For any user facing change, submit a link to a PR on the docs repo at https://gi } ``` -Be sure to test the integration with your adserver using the [Hello World](/integrationExamples/gpt/hello_world.html) sample page. --> +Be sure to test the integration with your adserver using the [Hello World](https://github.com/prebid/Prebid.js/blob/master/integrationExamples/gpt/hello_world.html) sample page. --> ## Other information diff --git a/.github/codeql/codeql-config.yml b/.github/codeql/codeql-config.yml index 2e8465003e4..8d3788e8956 100644 --- a/.github/codeql/codeql-config.yml +++ b/.github/codeql/codeql-config.yml @@ -2,3 +2,6 @@ paths: - src - modules - libraries +queries: + - name: Prebid queries + uses: ./.github/codeql/queries diff --git a/.github/codeql/queries/deviceMemory.ql b/.github/codeql/queries/deviceMemory.ql new file mode 100644 index 00000000000..6f650abf0e1 --- /dev/null +++ b/.github/codeql/queries/deviceMemory.ql @@ -0,0 +1,14 @@ +/** + * @id prebid/device-memory + * @name Access to navigator.deviceMemory + * @kind problem + * @problem.severity warning + * @description Finds uses of deviceMemory + */ + +import prebid + +from SourceNode nav +where + nav = windowPropertyRead("navigator") +select nav.getAPropertyRead("deviceMemory"), "deviceMemory is an indicator of fingerprinting" diff --git a/.github/codeql/queries/hardwareConcurrency.ql b/.github/codeql/queries/hardwareConcurrency.ql new file mode 100644 index 00000000000..350dbd1ae81 --- /dev/null +++ b/.github/codeql/queries/hardwareConcurrency.ql @@ -0,0 +1,14 @@ +/** + * @id prebid/hardware-concurrency + * @name Access to navigator.hardwareConcurrency + * @kind problem + * @problem.severity warning + * @description Finds uses of hardwareConcurrency + */ + +import prebid + +from SourceNode nav +where + nav = windowPropertyRead("navigator") +select nav.getAPropertyRead("hardwareConcurrency"), "hardwareConcurrency is an indicator of fingerprinting" diff --git a/.github/codeql/queries/prebid.qll b/.github/codeql/queries/prebid.qll new file mode 100644 index 00000000000..02fb5adc93c --- /dev/null +++ b/.github/codeql/queries/prebid.qll @@ -0,0 +1,36 @@ +import javascript +import DataFlow + +SourceNode otherWindow() { + result = globalVarRef("top") or + result = globalVarRef("self") or + result = globalVarRef("parent") or + result = globalVarRef("frames").getAPropertyRead() or + result = DOM::documentRef().getAPropertyRead("defaultView") +} + +SourceNode connectedWindow(SourceNode win) { + result = win.getAPropertyRead("self") or + result = win.getAPropertyRead("top") or + result = win.getAPropertyRead("parent") or + result = win.getAPropertyRead("frames").getAPropertyRead() or + result = win.getAPropertyRead("document").getAPropertyRead("defaultView") +} + +SourceNode relatedWindow(SourceNode win) { + result = connectedWindow(win) or + result = relatedWindow+(connectedWindow(win)) +} + +SourceNode anyWindow() { + result = otherWindow() or + result = relatedWindow(otherWindow()) +} + +/* + Matches uses of property `prop` done on any window object. +*/ +SourceNode windowPropertyRead(string prop) { + result = globalVarRef(prop) or + result = anyWindow().getAPropertyRead(prop) +} diff --git a/.github/codeql/queries/qlpack.yml b/.github/codeql/queries/qlpack.yml new file mode 100644 index 00000000000..72e90d3de9b --- /dev/null +++ b/.github/codeql/queries/qlpack.yml @@ -0,0 +1,8 @@ +--- +library: false +warnOnImplicitThis: false +name: queries +version: 0.0.1 +dependencies: + codeql/javascript-all: ^1.1.1 + codeql/javascript-queries: ^1.1.0 diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml index a3246cffd6d..5876dfa0138 100644 --- a/.github/release-drafter.yml +++ b/.github/release-drafter.yml @@ -1,6 +1,10 @@ name-template: 'Prebid $RESOLVED_VERSION Release' tag-template: '$RESOLVED_VERSION' +autolabeler: + - label: 'maintenance' + title: + - '/^(?!.*(bug|initial|release|fix)).*$/i' categories: - title: '🚀 New Features' label: 'feature' diff --git a/.github/workflows/jscpd.yml b/.github/workflows/jscpd.yml new file mode 100644 index 00000000000..de5f1408dff --- /dev/null +++ b/.github/workflows/jscpd.yml @@ -0,0 +1,124 @@ +name: Check for Duplicated Code + +on: + pull_request_target: + branches: + - master + +jobs: + check-duplication: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 # Fetch all history for all branches + ref: ${{ github.event.pull_request.head.sha }} + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: | + npm install -g jscpd diff-so-fancy + + - name: Create jscpd config file + run: | + echo '{ + "threshold": 20, + "minTokens": 100, + "reporters": [ + "json" + ], + "output": "./", + "pattern": "**/*.js", + "ignore": "**/*spec.js" + }' > .jscpd.json + + - name: Run jscpd on entire codebase + run: jscpd + + - name: Fetch base and target branches + run: | + git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} + git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/pull/${{ github.event.pull_request.number }}/merge + + - name: Get the diff + run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge > changed_files.txt + + - name: List generated files (debug) + run: ls -l + + - name: Upload unfiltered jscpd report + if: always() + uses: actions/upload-artifact@v4 + with: + name: unfiltered-jscpd-report + path: ./jscpd-report.json + + - name: Filter jscpd report for changed files + run: | + if [ ! -f ./jscpd-report.json ]; then + echo "jscpd-report.json not found" + exit 1 + fi + echo "Filtering jscpd report for changed files..." + CHANGED_FILES=$(jq -R -s -c 'split("\n")[:-1]' changed_files.txt) + echo "Changed files: $CHANGED_FILES" + jq --argjson changed_files "$CHANGED_FILES" ' + .duplicates | map(select( + (.firstFile?.name as $fname | $changed_files | any(. == $fname)) or + (.secondFile?.name as $sname | $changed_files | any(. == $sname)) + )) + ' ./jscpd-report.json > filtered-jscpd-report.json + cat filtered-jscpd-report.json + + - name: Check if filtered jscpd report exists + id: check_filtered_report + run: | + if [ $(wc -l < ./filtered-jscpd-report.json) -gt 1 ]; then + echo "filtered_report_exists=true" >> $GITHUB_ENV + else + echo "filtered_report_exists=false" >> $GITHUB_ENV + fi + + - name: Upload filtered jscpd report + if: env.filtered_report_exists == 'true' + uses: actions/upload-artifact@v4 + with: + name: filtered-jscpd-report + path: ./filtered-jscpd-report.json + + - name: Post GitHub comment + if: env.filtered_report_exists == 'true' + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const filteredReport = JSON.parse(fs.readFileSync('filtered-jscpd-report.json', 'utf8')); + let comment = "Whoa there, partner! 🌵🤠 We wrangled some duplicated code in your PR:\n\n"; + function link(dup) { + return `https://github.com/${{ github.event.repository.full_name }}/blob/${{ github.event.pull_request.head.sha }}/${dup.name}#L${dup.start + 1}-L${dup.end - 1}` + } + filteredReport.forEach(duplication => { + const firstFile = duplication.firstFile; + const secondFile = duplication.secondFile; + const lines = duplication.lines; + comment += `- [\`${firstFile.name}\`](${link(firstFile)}) has ${lines} duplicated lines with [\`${secondFile.name}\`](${link(secondFile)})\n`; + }); + comment += "\nReducing code duplication by importing common functions from a library not only makes our code cleaner but also easier to maintain. Please move the common code from both files into a library and import it in each. We hate that we have to mention this, however, commits designed to hide from this utility by renaming variables or reordering an object are poor conduct. We will not look upon them kindly! Keep up the great work! 🚀"; + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + + - name: Fail if duplications are found + if: env.filtered_report_exists == 'true' + run: | + echo "Duplications found, failing the check." + exit 1 diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml new file mode 100644 index 00000000000..03ef6478f1c --- /dev/null +++ b/.github/workflows/linter.yml @@ -0,0 +1,110 @@ +name: Check for linter warnings / exceptions + +on: + pull_request_target: + branches: + - master + +jobs: + check-linter: + runs-on: ubuntu-latest + + steps: + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + ref: ${{ github.event.pull_request.base.sha }} + + - name: Fetch base and target branches + run: | + git fetch origin +refs/heads/${{ github.event.pull_request.base.ref }}:refs/remotes/origin/${{ github.event.pull_request.base.ref }} + git fetch origin +refs/pull/${{ github.event.pull_request.number }}/merge:refs/remotes/pull/${{ github.event.pull_request.number }}/merge + + - name: Install dependencies + run: npm ci + + - name: Get the diff + run: git diff --name-only origin/${{ github.event.pull_request.base.ref }}...refs/remotes/pull/${{ github.event.pull_request.number }}/merge | grep '^\(modules\|src\|libraries\|creative\)/.*\.js$' > __changed_files.txt || true + + - name: Run linter on base branch + run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __base.json || true + + - name: Check out PR + run: git checkout ${{ github.event.pull_request.head.sha }} + + - name: Install dependencies + run: npm ci + + - name: Run linter on PR + run: npx eslint --no-inline-config --format json $(cat __changed_files.txt | xargs stat --printf '%n\n' 2> /dev/null) > __pr.json || true + + - name: Compare them and post comment if necessary + uses: actions/github-script@v7 + with: + script: | + const fs = require('fs'); + const path = require('path'); + const process = require('process'); + + function parse(fn) { + return JSON.parse(fs.readFileSync(fn)).reduce((memo, data) => { + const file = path.relative(process.cwd(), data.filePath); + if (!memo.hasOwnProperty(file)) { memo[file] = { errors: 0, warnings: 0} } + data.messages.forEach(({severity}) => { + memo[file][severity > 1 ? 'errors' : 'warnings']++; + }); + return memo; + }, {}) + } + + function mkDiff(old, new_) { + const files = Object.fromEntries( + Object.entries(new_) + .map(([file, {errors, warnings}]) => { + const {errors: oldErrors, warnings: oldWarnings} = old[file] || {}; + return [file, {errors: Math.max(0, errors - (oldErrors ?? 0)), warnings: Math.max(0, warnings - (oldWarnings ?? 0))}] + }) + .filter(([_, {errors, warnings}]) => errors > 0 || warnings > 0) + ) + return Object.values(files).reduce((memo, {warnings, errors}) => { + memo.errors += errors; + memo.warnings += warnings; + return memo; + }, {errors: 0, warnings: 0, files}) + } + + function mkComment({errors, warnings, files}) { + function pl(noun, number) { + return noun + (number === 1 ? '' : 's') + } + if (errors === 0 && warnings === 0) return; + const summary = []; + if (errors) summary.push(`**${errors}** linter ${pl('error', errors)}`) + if (warnings) summary.push(`**${warnings}** linter ${pl('warning', warnings)}`) + let cm = `Tread carefully! This PR adds ${summary.join(' and ')} (possibly disabled through directives):\n\n`; + Object.entries(files).forEach(([file, {errors, warnings}]) => { + const summary = []; + if (errors) summary.push(`+${errors} ${pl('error', errors)}`); + if (warnings) summary.push(`+${warnings} ${pl('warning', warnings)}`) + cm += ` * \`${file}\` (${summary.join(', ')})\n` + }) + return cm; + } + + const [base, pr] = ['__base.json', '__pr.json'].map(parse); + const comment = mkComment(mkDiff(base, pr)); + + if (comment) { + github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: context.issue.number, + body: comment + }); + } diff --git a/.nvmrc b/.nvmrc index 66df3b7ab2d..f203ab89b79 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -12.16.1 +20.13.1 diff --git a/PR_REVIEW.md b/PR_REVIEW.md index 9deac9963fb..f6a2c157d2d 100644 --- a/PR_REVIEW.md +++ b/PR_REVIEW.md @@ -23,10 +23,11 @@ General gulp commands include separate commands for serving the codebase on a bu - Checkout the branch (these instructions are available on the GitHub PR page as well). - Verify PR is a single change type. Example, refactor OR bugfix. If more than 1 type, ask submitter to break out requests. - Verify code under review has at least 80% unit test coverage. If legacy code doesn't have enough unit test coverage, require that additional unit tests to be included in the PR. -- Verify tests are green in Travis-ci + local build by running `gulp serve` | `gulp test` +- Verify tests are green in circle-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. +- Don't allow needless code duplication with other js files; require both files import common code. Do not allow commits designed to fool the code duplication checker. - 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. - 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. @@ -51,20 +52,21 @@ Follow steps above for general review process. In addition, please verify the fo - If the adapter being submitted is an alias type, check with the bidder contact that is being aliased to make sure it's allowed. - All bidder parameter conventions must be followed: - Video params must be read from AdUnit.mediaTypes.video when available; however bidder config can override the ad unit. - - First party data must be read from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd). + - First party data must be read from the bid request object: bidrequest.ortb2 - 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 bidderRequest.refererInfo.referer must be checked in addition to any bidder-specific parameter. - Page position must come from bidrequest.mediaTypes.banner.pos or bidrequest.mediaTypes.video.pos - - Global OpenRTB fields should come from [getConfig('ortb2');](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-fpd): + - Eids object is to be preferred to Userids object in the bid request, as the userid object may be removed in a future version + - Global OpenRTB fields should come from bidrequest.ortb2 - bcat, battr, badv - Impression-specific OpenRTB fields should come from bidrequest.ortb2imp - instl - Below are some examples of bidder specific updates that should require docs update (in their dev-docs/bidders/BIDDER.md file): - - If they support the GDPR consentManagement module and TCF1, add `gdpr_supported: true` - - If they support the GDPR consentManagement module and TCF2, add `tcf2_supported: true` + - If they support the TCF consentManagementTcf module and TCF2, add `tcf2_supported: true` - If they support the US Privacy consentManagementUsp module, add `usp_supported: true` - - If they support one or more userId modules, add `userId: (list of supported vendors)` + - If they support the GPP consentManagementGpp module, add `gpp_supported: true` + - If they support one or more userId modules, add `userId: (list of supported vendors) or (all)` - If they support video and/or native mediaTypes add `media_types: video, native`. Note that display is added by default. If you don't support display, add "no-display" as the first entry, e.g. `media_types: no-display, native` - If they support COPPA, add `coppa_supported: true` - If they support SChain, add `schain_supported: true` @@ -100,7 +102,7 @@ Follow steps above for general review process. In addition: - 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) +- 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). This ability to write will be removed in a future version, see https://github.com/prebid/Prebid.js/issues/10710 - 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 @@ -121,6 +123,7 @@ Follow steps above for general review process. In addition: - 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 practicing reasonable data minimization, eg not sending all eids over the wire without publisher whitelisting - 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. diff --git a/README.md b/README.md index f890f055104..65dc668164e 100644 --- a/README.md +++ b/README.md @@ -1,5 +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") +[![Percentage of issues still open](http://isitmaintained.com/badge/open/prebid/Prebid.js.svg)](https://isitmaintained.com/project/prebid/Prebid.js "Percentage of issues still open") [![Coverage Status](https://coveralls.io/repos/github/prebid/Prebid.js/badge.svg)](https://coveralls.io/github/prebid/Prebid.js) # Prebid.js @@ -7,8 +7,8 @@ > A free and open source library for publishers to quickly implement header bidding. 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). +Additional documentation can be found at [the Prebid.js documentation homepage](https://docs.prebid.org/prebid/prebidjs.html). +Working examples can be found in [the developer docs](https://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. @@ -26,7 +26,7 @@ Prebid.js is open source software that is offered for free as a convenience. Whi *Note:* Requires Prebid.js v1.38.0+ -Prebid.js depends on Babel and some Babel Plugins in order to run correctly in the browser. Here are some examples for +Prebid.js depends on Babel and some Babel Plugins in order to run correctly in the browser. Here are some examples for configuring webpack to work with Prebid.js. With Babel 7: @@ -37,7 +37,7 @@ module.exports = { mode: 'production', module: { rules: [ - + // this rule can be excluded if you don't require babel-loader for your other application files { test: /\.m?js$/, @@ -46,7 +46,7 @@ module.exports = { loader: 'babel-loader', } }, - + // this separate rule is required to make sure that the Prebid.js files are babel-ified. this rule will // override the regular exclusion from above (for being inside node_modules). { @@ -71,7 +71,7 @@ Or for Babel 6: // you must manually install and specify the presets and plugins yourself options: { plugins: [ - "transform-object-assign", // required (for IE support) and "babel-plugin-transform-object-assign" + "transform-object-assign", // required (for IE support) and "babel-plugin-transform-object-assign" // must be installed as part of your package. require('prebid.js/plugins/pbjsGlobals.js') // required! ], @@ -79,7 +79,7 @@ Or for Babel 6: ["env", { // you can use other presets if you wish. "targets": { // this example is using "babel-presets-env", which must be installed if you "browsers": [ // follow this example. - ... // your browser targets. they should probably match the targets you're using for the rest + ... // your browser targets. they should probably match the targets you're using for the rest // of your application ] } @@ -143,7 +143,7 @@ This will run testing but not linting. A web server will start at `http://localh Development may be a bit slower but if you prefer linting and additional watch files you can also still run just: - $ gulp serve + $ gulp serve ### Build Optimization @@ -162,11 +162,11 @@ Building with just these adapters will result in a smaller bundle which should a - Then run the build: $ gulp build --modules=openxBidAdapter,rubiconBidAdapter,sovrnBidAdapter - + Alternatively, a `.json` file can be specified that contains a list of modules you would like to include. $ gulp build --modules=modules.json - + With `modules.json` containing the following ```json modules.json [ @@ -202,7 +202,7 @@ gulp bundle --tag one --modules=one.json gulp bundle --tag two --modules=two.json ``` -This generates slightly larger files, but has the advantage of being much faster to run (after the initial `gulp build`). It's also the method used by [the Prebid.org download page](https://docs.prebid.org/download.html). +This generates slightly larger files, but has the advantage of being much faster to run (after the initial `gulp build`). It's also the method used by [the Prebid.org download page](https://docs.prebid.org/download.html). @@ -374,11 +374,11 @@ The results will be in *Note*: Starting in June 2016, all pull requests to Prebid.js need to include tests with greater than 80% code coverage before they can be merged. For more information, see [#421](https://github.com/prebid/Prebid.js/issues/421). -For instructions on writing tests for Prebid.js, see [Testing Prebid.js](http://prebid.org/dev-docs/testing-prebid.html). +For instructions on writing tests for Prebid.js, see [Testing Prebid.js](https://prebid.org/dev-docs/testing-prebid.html). ### Supported Browsers -Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11. +Prebid.js is supported on IE11 and modern browsers until 5.x. 6.x+ transpiles to target >0.25%; not Opera Mini; not IE11. ### Governance Review our governance model [here](https://github.com/prebid/Prebid.js/tree/master/governance.md). diff --git a/allowedModules.js b/allowedModules.js index bc9ada39571..dbcae2db2cc 100644 --- a/allowedModules.js +++ b/allowedModules.js @@ -1,7 +1,6 @@ module.exports = { 'modules': [ - 'criteo-direct-rsa-validate', 'crypto-js', 'live-connect' // Maintained by LiveIntent : https://github.com/liveintent-berlin/live-connect/ ], diff --git a/browsers.json b/browsers.json index bd6bd5772d6..0649a13e873 100644 --- a/browsers.json +++ b/browsers.json @@ -1,39 +1,39 @@ { - "bs_edge_latest_windows_10": { + "bs_edge_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "edge", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_latest_windows_10": { + "bs_chrome_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "chrome", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_chrome_87_windows_10": { + "bs_chrome_107_windows_10": { "base": "BrowserStack", "os_version": "10", "browser": "chrome", - "browser_version": "87.0", + "browser_version": "107.0", "device": null, "os": "Windows" }, - "bs_firefox_latest_windows_10": { + "bs_firefox_latest_windows_11": { "base": "BrowserStack", - "os_version": "10", + "os_version": "11", "browser": "firefox", "browser_version": "latest", "device": null, "os": "Windows" }, - "bs_safari_latest_mac_bigsur": { + "bs_safari_latest_mac": { "base": "BrowserStack", - "os_version": "Big Sur", + "os_version": "Sonoma", "browser": "safari", "browser_version": "latest", "device": null, @@ -41,11 +41,11 @@ }, "bs_safari_15_catalina": { "base": "BrowserStack", - "os_version": "Catalina", + "os_version": "Monterey", "browser": "safari", - "browser_version": "13.1", + "browser_version": "15.6", "device": null, "os": "OS X" } - + } diff --git a/creative/constants.js b/creative/constants.js index d02c4c9d5e4..5f807c69f87 100644 --- a/creative/constants.js +++ b/creative/constants.js @@ -1,6 +1,7 @@ // eslint-disable-next-line prebid/validate-imports -import { AD_RENDER_FAILED_REASON, EVENTS, MESSAGES } from '../src/constants.js'; +import {AD_RENDER_FAILED_REASON, EVENTS, MESSAGES} from '../src/constants.js'; +export {PB_LOCATOR} from '../src/constants.js'; export const MESSAGE_REQUEST = MESSAGES.REQUEST; export const MESSAGE_RESPONSE = MESSAGES.RESPONSE; export const MESSAGE_EVENT = MESSAGES.EVENT; diff --git a/creative/crossDomain.js b/creative/crossDomain.js index a851885bfc0..d3524f61d4b 100644 --- a/creative/crossDomain.js +++ b/creative/crossDomain.js @@ -1,9 +1,11 @@ import { ERROR_EXCEPTION, - EVENT_AD_RENDER_FAILED, EVENT_AD_RENDER_SUCCEEDED, + EVENT_AD_RENDER_FAILED, + EVENT_AD_RENDER_SUCCEEDED, MESSAGE_EVENT, MESSAGE_REQUEST, - MESSAGE_RESPONSE + MESSAGE_RESPONSE, + PB_LOCATOR } from './constants.js'; const mkFrame = (() => { @@ -24,14 +26,27 @@ const mkFrame = (() => { }; })(); +function isPrebidWindow(win) { + return !!win.frames[PB_LOCATOR]; +} + export function renderer(win) { + let target = win.parent; + try { + while (target !== win.top && !isPrebidWindow(target)) { + target = target.parent; + } + if (!isPrebidWindow(target)) target = win.parent; + } catch (e) { + } + return function ({adId, pubUrl, clickUrl}) { const pubDomain = new URL(pubUrl, window.location).origin; function sendMessage(type, payload, responseListener) { const channel = new MessageChannel(); channel.port1.onmessage = guard(responseListener); - win.parent.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]); + target.postMessage(JSON.stringify(Object.assign({message: type, adId}, payload)), pubDomain, [channel.port2]); } function onError(e) { @@ -77,7 +92,7 @@ export function renderer(win) { W.Promise.resolve(W.render(data, {sendMessage, mkFrame}, win)).then( () => sendMessage(MESSAGE_EVENT, {event: EVENT_AD_RENDER_SUCCEEDED}), onError - ) + ); }); win.document.body.appendChild(renderer); } diff --git a/gulpHelpers.js b/gulpHelpers.js index 1eec08b7a3e..d68b00d799b 100644 --- a/gulpHelpers.js +++ b/gulpHelpers.js @@ -58,6 +58,14 @@ module.exports = { }); } + try { + const moduleAliases = JSON.parse( + fs.readFileSync('module-alias.json', 'utf8') + ); + + modules = modules.map(module => moduleAliases[module] || module); + } catch (_e) {} + // we need to forcefuly include the parentModule if the subModule is present in modules list and parentModule is not present in modules list Object.keys(submodules).forEach(parentModule => { if ( diff --git a/gulpfile.js b/gulpfile.js index 86c1b7fe509..a32a2d11ce6 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -147,6 +147,17 @@ function makeVerbose(config = webpackConfig) { }); } +function prebidSource(webpackCfg) { + var externalModules = helpers.getArgModules(); + + const analyticsSources = helpers.getAnalyticsSources(); + const moduleSources = helpers.getModulePaths(externalModules); + + return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) + .pipe(helpers.nameModules(externalModules)) + .pipe(webpackStream(webpackCfg, webpack)); +} + function makeDevpackPkg(config = webpackConfig) { return function() { var cloned = _.cloneDeep(config); @@ -163,14 +174,7 @@ function makeDevpackPkg(config = webpackConfig) { .filter((use) => use.loader === 'babel-loader') .forEach((use) => use.options = Object.assign({}, use.options, babelConfig)); - var externalModules = helpers.getArgModules(); - - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); - - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) + return prebidSource(cloned) .pipe(gulp.dest('build/dev')) .pipe(connect.reload()); } @@ -183,14 +187,7 @@ function makeWebpackPkg(config = webpackConfig) { } return function buildBundle() { - var externalModules = helpers.getArgModules(); - - const analyticsSources = helpers.getAnalyticsSources(); - const moduleSources = helpers.getModulePaths(externalModules); - - return gulp.src([].concat(moduleSources, analyticsSources, 'src/prebid.js')) - .pipe(helpers.nameModules(externalModules)) - .pipe(webpackStream(cloned, webpack)) + return prebidSource(cloned) .pipe(gulp.dest('build/dist')); } } @@ -413,7 +410,9 @@ function runKarma(options, done) { // the karma server appears to leak memory; starting it multiple times in a row will run out of heap // here we run it in a separate process to bypass the problem options = Object.assign({browsers: helpers.parseBrowserArgs(argv)}, options) - const child = fork('./karmaRunner.js'); + const child = fork('./karmaRunner.js', null, { + env: Object.assign({}, options.env, process.env) + }); child.on('exit', (exitCode) => { if (exitCode) { done(new Error('Karma tests failed with exit code ' + exitCode)); @@ -426,7 +425,15 @@ function runKarma(options, done) { // If --file "" is given, the task will only run tests in the specified file. function testCoverage(done) { - runKarma({coverage: true, browserstack: false, watch: false, file: argv.file}, done); + runKarma({ + coverage: true, + browserstack: false, + watch: false, + file: argv.file, + env: { + NODE_OPTIONS: '--max-old-space-size=8096' + } + }, done); } function coveralls() { // 2nd arg is a dependency: 'test' must be finished diff --git a/integrationExamples/gpt/51DegreesRtdProvider_example.html b/integrationExamples/gpt/51DegreesRtdProvider_example.html new file mode 100644 index 00000000000..7864f2e05f5 --- /dev/null +++ b/integrationExamples/gpt/51DegreesRtdProvider_example.html @@ -0,0 +1,195 @@ + + + + + + + + + + + + 51Degrees RTD submodule example - Prebid.js + + +

51Degrees RTD submodule - example of usage

+ +

div-banner-native-1

+
+

No response

+ +
+ +

div-banner-native-2

+
+

No response

+ +
+ +
+

Testing/Debugging Guidance

+
    +
  1. Make sure you have debug: true under pbjs.setConfig in this example code (be sure to remove it for production!) +
  2. Make sure you have replaced <YOUR RESOURCE KEY> in this example code with the one you have obtained + from the 51Degrees Configurator Tool
  3. +
  4. Open DevTools Console in your browser and refresh the page
  5. +
  6. Observe the enriched ortb device data shown below and also in the console as part of the [51Degrees RTD Submodule]: reqBidsConfigObj: message (under reqBidsConfigObj.global.device)
  7. +
+
+ + + diff --git a/integrationExamples/gpt/adnuntius_multiformat_example.html b/integrationExamples/gpt/adnuntius_multiformat_example.html new file mode 100644 index 00000000000..87b30d5887a --- /dev/null +++ b/integrationExamples/gpt/adnuntius_multiformat_example.html @@ -0,0 +1,132 @@ + + + + + + + +

Adnuntius NATIVE

+
Ad Slot 1
+ + +
+ +
+ + + diff --git a/integrationExamples/gpt/contxtfulRtdProvider_example.html b/integrationExamples/gpt/contxtfulRtdProvider_example.html index 29284de81a2..e47bd4142f9 100644 --- a/integrationExamples/gpt/contxtfulRtdProvider_example.html +++ b/integrationExamples/gpt/contxtfulRtdProvider_example.html @@ -1,91 +1,212 @@ + - - - - + +Contxtful Rtd Provider Example + - +googletag.cmd.push(function () { + googletag + .defineSlot('/19968336/header-bid-tag-1', + div2Sizes, 'div-2') + .addService(googletag.pubads()); + googletag.pubads().enableSingleRequest(); + googletag.enableServices(); +}); + + -

Contxtful RTD Provider

-
- - +

Contxtful Rtd Provider Example

+ +

+ +
Div-1
+
+ +
+ +
+ +
Div-2
+
+ +
+ + diff --git a/integrationExamples/gpt/creative_rendering.html b/integrationExamples/gpt/creative_rendering.html deleted file mode 100644 index 04d4736c631..00000000000 --- a/integrationExamples/gpt/creative_rendering.html +++ /dev/null @@ -1,15 +0,0 @@ - - diff --git a/integrationExamples/gpt/cstg_example.html b/integrationExamples/gpt/cstg_example.html index 8ca049a0ed0..2ca8b43b9de 100644 --- a/integrationExamples/gpt/cstg_example.html +++ b/integrationExamples/gpt/cstg_example.html @@ -29,8 +29,8 @@ function updateUID2GuiElements() { console.log('Updating displayed values.'); const uid2 = pbjs.getUserIds().uid2; - document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 ? 'yes' : 'no'; - document.querySelector('#uid2AdvertisingToken').innerText = uid2 ? String(uid2.id) : ''; + document.querySelector('#uid2TargetedAdvertisingReady').innerText = uid2 && !uid2.optout ? 'yes' : 'no'; + document.querySelector('#uid2AdvertisingToken').innerText = uid2 && !uid2.optout ? String(uid2.id) : uid2 && uid2.optout ? 'Optout' : ''; if (!uid2) { document.querySelector('#uid2LoginForm').style['display'] = 'block'; document.querySelector('#uid2ClearStorageForm').style['display'] = 'none';; diff --git a/integrationExamples/gpt/fledge_example.html b/integrationExamples/gpt/paapi_example.html similarity index 97% rename from integrationExamples/gpt/fledge_example.html rename to integrationExamples/gpt/paapi_example.html index 5a6ab7a5fef..860d7c22edf 100644 --- a/integrationExamples/gpt/fledge_example.html +++ b/integrationExamples/gpt/paapi_example.html @@ -3,7 +3,7 @@ diff --git a/integrationExamples/gpt/prebidServer_fledge_example.html b/integrationExamples/gpt/prebidServer_paapi_example.html similarity index 91% rename from integrationExamples/gpt/prebidServer_fledge_example.html rename to integrationExamples/gpt/prebidServer_paapi_example.html index eb2fc438997..d138d2b7753 100644 --- a/integrationExamples/gpt/prebidServer_fledge_example.html +++ b/integrationExamples/gpt/prebidServer_paapi_example.html @@ -3,7 +3,7 @@ @@ -44,8 +44,8 @@ pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { - enabled: true + paapi: { + enabled: true, }, s2sConfig: [{ accountId : '1', @@ -57,13 +57,6 @@ }] }); - pbjs.setBidderConfig({ - bidders: ['openx'], - config: { - fledgeEnabled: true - } - }); - pbjs.addAdUnits(adUnits); pbjs.requestBids({ diff --git a/integrationExamples/gpt/precisoExample.html b/integrationExamples/gpt/precisoExample.html new file mode 100644 index 00000000000..04f44b28345 --- /dev/null +++ b/integrationExamples/gpt/precisoExample.html @@ -0,0 +1,170 @@ + + + + + + + + + + + + + +

Basic Prebid.js Example with Preciso Bidder

+

Adslot-1

+
+ +
+ +
+

Adslot-2

+ +
+ + + + diff --git a/integrationExamples/gpt/precisonativeExample.html b/integrationExamples/gpt/precisonativeExample.html new file mode 100644 index 00000000000..4e7009e2c58 --- /dev/null +++ b/integrationExamples/gpt/precisonativeExample.html @@ -0,0 +1,203 @@ + + + + + + + + + + + + + + + + +

Ad Serverless Test Page

+

+ Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's + standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make + a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, + remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing + Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions + of Lorem Ipsum +

+
+

+ Contrary to popular belief, Lorem Ipsum is not simply random text. It has roots in a piece of classical Latin + literature from 45 BC, making it over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney + College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and + going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum + comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by + Cicero, written in 45 BC. This book is a treatise on the theory of ethics, very popular during the Renaissance. + The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32. +

+
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/raynRtdProvider_example.html b/integrationExamples/gpt/raynRtdProvider_example.html index 2d43c37513a..7965daa6e85 100644 --- a/integrationExamples/gpt/raynRtdProvider_example.html +++ b/integrationExamples/gpt/raynRtdProvider_example.html @@ -6,6 +6,7 @@ "3": ["264", "267", "261"], "4": ["438"] }, + "103015": ['agdv23', 'avscg3'], "903555595": { "7": { "2": ["51", "246"] diff --git a/integrationExamples/gpt/rewardedInterestIdSystem_example.html b/integrationExamples/gpt/rewardedInterestIdSystem_example.html new file mode 100644 index 00000000000..c9730f354b3 --- /dev/null +++ b/integrationExamples/gpt/rewardedInterestIdSystem_example.html @@ -0,0 +1,114 @@ + + + + + Rewarded Interest ID Example + + + + + + + + + + + + + +

Rewarded Interest ID Example

+ +

Generated IDs:

+

+
+

Generated EIDs

+

+
+
+
diff --git a/integrationExamples/gpt/idward_segments_example.html b/integrationExamples/gpt/symitridap_segments_example.html
similarity index 82%
rename from integrationExamples/gpt/idward_segments_example.html
rename to integrationExamples/gpt/symitridap_segments_example.html
index 9bc06124c77..1f5a654cfdb 100644
--- a/integrationExamples/gpt/idward_segments_example.html
+++ b/integrationExamples/gpt/symitridap_segments_example.html
@@ -1,7 +1,7 @@
 
 
     
-    
+    
     
 
-
First Party Data (ortb2) Sent to Bidding Adapter
+
Segments Sent to Bidding Adapter
diff --git a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html b/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html deleted file mode 100644 index 9a4991d2711..00000000000 --- a/integrationExamples/gpt/top-level-paapi/tl_paapi_example.html +++ /dev/null @@ -1,188 +0,0 @@ - - - - - - - - - - - -

Standalone PAAPI Prebid.js Example

-

Start local server with:

-gulp serve-fast --https -

Chrome flags:

---enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true - --privacy-sandbox-enrollment-overrides=https://localhost:9999 -

Join interest group at https://privacysandbox.openx.net/fledge/advertiser -

-
Div-1
-
- -
- - - diff --git a/integrationExamples/gpt/weboramaRtdProvider_example.html b/integrationExamples/gpt/weboramaRtdProvider_example.html index 7e75721103f..82b97696371 100644 --- a/integrationExamples/gpt/weboramaRtdProvider_example.html +++ b/integrationExamples/gpt/weboramaRtdProvider_example.html @@ -1,187 +1,201 @@ - - - - weborama rtd submodule example - - - - + + + + weborama rtd submodule example + + +
-

- test webo rtd submodule with prebid.js -

+

test webo rtd submodule with prebid.js

Basic Prebid.js Example

Div-1
-
- +
+
- - - - \ No newline at end of file + + diff --git a/integrationExamples/gpt/wurflRtdProvider_example.html b/integrationExamples/gpt/wurflRtdProvider_example.html new file mode 100644 index 00000000000..f2bfe7f76b7 --- /dev/null +++ b/integrationExamples/gpt/wurflRtdProvider_example.html @@ -0,0 +1,106 @@ + + + + + + + + + + + + + + +

Prebid.js Test

+
Div-1
+
+ +
+ + + \ No newline at end of file diff --git a/integrationExamples/gpt/x-domain/creative.html b/integrationExamples/gpt/x-domain/creative.html index 1ce1a206474..b7ba6b36a79 100644 --- a/integrationExamples/gpt/x-domain/creative.html +++ b/integrationExamples/gpt/x-domain/creative.html @@ -2,7 +2,7 @@ // creative will be rendered, e.g. GAM delivering a SafeFrame // this code is autogenerated, also available in 'build/creative/creative.js' - + + + + + + + + + +

GAM contextual + Publisher as top level PAAPI seller example

+ +

+ This example starts PAAPI auctions at the same time as GAM targeting. The flow is + similar to a typical GAM auction, but if Prebid wins, and got a + PAAPI bid, it is rendered instead of the contextual bid. +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+gulp serve-fast --https +

Chrome flags:

+--enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/top-level-paapi/no_adserver.html b/integrationExamples/top-level-paapi/no_adserver.html new file mode 100644 index 00000000000..dd363e53485 --- /dev/null +++ b/integrationExamples/top-level-paapi/no_adserver.html @@ -0,0 +1,114 @@ + + + + + + + + + +

No ad server, publisher as top level PAAPI seller example

+ +

+ +

+
+ +
+
Div-1
+
+ +
+
+

Instructions

+

Start local server with:

+ gulp serve-fast --https +

Chrome flags:

+ --enable-features=CookieDeprecationFacilitatedTesting:label/treatment_1.2/force_eligible/true + --privacy-sandbox-enrollment-overrides=https://localhost:9999 +

Join interest group at https://www.optable.co/ +

+
+ + diff --git a/integrationExamples/gpt/top-level-paapi/decisionLogic.js b/integrationExamples/top-level-paapi/shared/decisionLogic.js similarity index 100% rename from integrationExamples/gpt/top-level-paapi/decisionLogic.js rename to integrationExamples/top-level-paapi/shared/decisionLogic.js diff --git a/integrationExamples/top-level-paapi/shared/example-setup.js b/integrationExamples/top-level-paapi/shared/example-setup.js new file mode 100644 index 00000000000..1c52abf02c9 --- /dev/null +++ b/integrationExamples/top-level-paapi/shared/example-setup.js @@ -0,0 +1,95 @@ +// intercept navigator.runAdAuction and print parameters to console +(() => { + var originalRunAdAuction = navigator.runAdAuction; + navigator.runAdAuction = function (...args) { + console.log('%c runAdAuction', 'background: cyan; border: 2px; border-radius: 3px', ...args); + return originalRunAdAuction.apply(navigator, args); + }; +})(); +init(); +setupContextualResponse(); + +function addExampleControls(requestBids) { + const ctl = document.createElement('div'); + ctl.innerHTML = ` + + Simulate contextual bid: + + CPM + + + `; + ctl.style = 'margin-top: 30px'; + document.body.appendChild(ctl); + ctl.querySelector('.bid').addEventListener('click', function (ev) { + const cpm = ctl.querySelector('.cpm').value; + if (cpm) { + setupContextualResponse(parseInt(cpm, 10)); + } + requestBids(); + }); +} + +function init() { + window.pbjs = window.pbjs || {que: []}; + window.pbjs.que.push(() => { + pbjs.aliasBidder('optable', 'contextual'); + [ + 'auctionInit', + 'auctionTimeout', + 'auctionEnd', + 'bidAdjustment', + 'bidTimeout', + 'bidRequested', + 'bidResponse', + 'bidRejected', + 'noBid', + 'seatNonBid', + 'bidWon', + 'bidderDone', + 'bidderError', + 'setTargeting', + 'beforeRequestBids', + 'beforeBidderHttp', + 'requestBids', + 'addAdUnits', + 'adRenderFailed', + 'adRenderSucceeded', + 'tcf2Enforcement', + 'auctionDebug', + 'bidViewable', + 'staleRender', + 'billableEvent', + 'bidAccepted', + 'paapiRunAuction', + 'paapiBid', + 'paapiNoBid', + 'paapiError', + ].forEach(evt => { + pbjs.onEvent(evt, (arg) => { + console.log('Event:', evt, arg); + }) + }); + }); +} + +function setupContextualResponse(cpm = 1) { + pbjs.que.push(() => { + pbjs.setConfig({ + debugging: { + enabled: true, + intercept: [ + { + when: { + bidder: 'contextual' + }, + then: { + cpm, + currency: 'USD' + } + } + ] + } + }); + }); +} diff --git a/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html new file mode 100644 index 00000000000..7c73312c5c3 --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/bidRequestScheduling.html @@ -0,0 +1,143 @@ + + + + + + + AdPlayer.Pro bid request scheduling + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/integrationExamples/videoModule/adPlayerPro/eventListeners.html b/integrationExamples/videoModule/adPlayerPro/eventListeners.html new file mode 100644 index 00000000000..3c26ef42bee --- /dev/null +++ b/integrationExamples/videoModule/adPlayerPro/eventListeners.html @@ -0,0 +1,154 @@ + + + + + + + AdPlayer.Pro Event Listeners + + + + + + + + +

AdPlayer.Pro Event Listeners

+ +
Div-1: Player placeholder div
+
+ + + diff --git a/karma.conf.maker.js b/karma.conf.maker.js index 7b8ca13b20b..fbef10ff567 100644 --- a/karma.conf.maker.js +++ b/karma.conf.maker.js @@ -108,14 +108,12 @@ function setBrowsers(karmaConf, browserstack) { module.exports = function(codeCoverage, browserstack, watchMode, file, disableFeatures) { var webpackConfig = newWebpackConfig(codeCoverage, disableFeatures); var plugins = newPluginsArray(browserstack); - - var files = file ? ['test/test_deps.js', file, 'test/helpers/hookSetup.js'].flatMap(f => f) : ['test/test_index.js']; - // This file opens the /debug.html tab automatically. - // It has no real value unless you're running --watch, and intend to do some debugging in the browser. - if (watchMode) { - files.push('test/helpers/karma-init.js'); + if (file) { + file = Array.isArray(file) ? ['test/pipeline_setup.js', ...file] : [file] } + var files = file ? ['test/test_deps.js', ...file, 'test/helpers/hookSetup.js'].flatMap(f => f) : ['test/test_index.js']; + var config = { // base path that will be used to resolve all patterns (eg. files, exclude) basePath: './', @@ -127,15 +125,15 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe }, // frameworks to use // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['es5-shim', 'mocha', 'chai', 'sinon'], + frameworks: ['es5-shim', 'mocha', 'chai', 'sinon', 'webpack'], - files: files, + // test files should not be watched or they'll run twice after an update + // (they are still, in fact, watched through autoWatch: true) + files: files.map(fn => ({pattern: fn, watched: false, served: true, included: true})), // preprocess matching files before serving them to the browser // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'test/test_index.js': ['webpack', 'sourcemap'] - }, + preprocessors: Object.fromEntries(files.map(f => [f, ['webpack', 'sourcemap']])), // web server port port: 9876, @@ -148,7 +146,7 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe logLevel: karmaConstants.LOG_INFO, // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, + autoWatch: watchMode, reporters: ['mocha'], @@ -175,15 +173,6 @@ module.exports = function(codeCoverage, browserstack, watchMode, file, disableFe 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/karmaRunner.js b/karmaRunner.js index 96259069966..7239d2a2556 100644 --- a/karmaRunner.js +++ b/karmaRunner.js @@ -1,23 +1,97 @@ const karma = require('karma'); const process = require('process'); const karmaConfMaker = require('./karma.conf.maker.js'); +const glob = require('glob'); +/** + * Environment variables: + * + * TEST_CHUNKS: number of chunks to split tests into, or MAX to run each test suite in isolation + * TEST_CHUNK: run only this chunk (e.g. TEST_CHUNKS=4 TEST_CHUNK=2 gulp test) will run only the second quarter + * TEST_ALL: set to continue running remaining chunks after a previous chunk failed + * TEST_PAT: test file pattern (default is *_spec.js) + */ -process.on('message', function(options) { - try { - let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, options.file, options.disableFeatures); +process.on('message', function (options) { + function info(msg) { + // eslint-disable-next-line no-console + console.log('\x1b[46m\x1b[30m%s\x1b[0m', msg); + } + + function error(msg) { + // eslint-disable-next-line no-console + console.log('\x1b[41m\x1b[37m%s\x1b[0m', msg); + } + + function chunkDesc(chunk) { + return chunk.length > 1 ? `From ${chunk[0]} to ${chunk[chunk.length - 1]}` : chunk[0]; + } + + const failures = []; + + function quit(fail) { + // eslint-disable-next-line no-console + console.log(''); + failures.forEach(([chunkNo, chunkTot, chunk]) => { + error(`Chunk ${chunkNo + 1} of ${chunkTot} failed: ${chunkDesc(chunk)}`); + fail = true; + }); + process.exit(fail ? 1 : 0); + } + + process.on('SIGINT', () => quit()); + + function runKarma(file) { + let cfg = karmaConfMaker(options.coverage, options.browserstack, options.watch, file, options.disableFeatures); if (options.browsers && options.browsers.length) { cfg.browsers = options.browsers; } if (options.oneBrowser) { - cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]] + cfg.browsers = [cfg.browsers.find((b) => b.toLowerCase().includes(options.oneBrowser.toLowerCase())) || cfg.browsers[0]]; } cfg = karma.config.parseConfig(null, cfg); - new karma.Server(cfg, (exitCode) => { - process.exit(exitCode); - }).start(); + return new Promise((resolve, reject) => { + new karma.Server(cfg, (exitCode) => { + exitCode ? reject(exitCode) : resolve(exitCode); + }).start(); + }); + } + + try { + let chunks = []; + if (options.file) { + chunks.push([options.file]); + } else { + const chunkNum = process.env['TEST_CHUNKS'] ?? 1; + const pat = process.env['TEST_PAT'] ?? '*_spec.js' + const tests = glob.sync('test/**/' + pat).sort(); + const chunkLen = chunkNum === 'MAX' ? 0 : Math.floor(tests.length / Number(chunkNum)); + chunks.push([]); + tests.forEach((fn) => { + chunks[chunks.length - 1].push(fn); + if (chunks[chunks.length - 1].length > chunkLen) chunks.push([]); + }); + chunks = chunks.filter(chunk => chunk.length > 0); + if (chunks.length > 1) { + info(`Splitting tests into ${chunkNum} chunks, ${chunkLen + 1} suites each`); + } + } + let pm = Promise.resolve(); + chunks.forEach((chunk, i) => { + if (process.env['TEST_CHUNK'] && Number(process.env['TEST_CHUNK']) !== i + 1) return; + pm = pm.then(() => { + info(`Starting chunk ${i + 1} of ${chunks.length}: ${chunkDesc(chunk)}`); + return runKarma(chunk); + }).catch(() => { + failures.push([i, chunks.length, chunk]); + if (!process.env['TEST_ALL']) quit(); + }).finally(() => { + info(`Chunk ${i + 1} of ${chunks.length}: done`); + }); + }); + pm.then(() => quit()); } catch (e) { // eslint-disable-next-line - console.error(e); - process.exit(1); + error(e); + quit(true); } }); diff --git a/libraries/adagioUtils/adagioUtils.js b/libraries/adagioUtils/adagioUtils.js new file mode 100644 index 00000000000..c2614c45d0c --- /dev/null +++ b/libraries/adagioUtils/adagioUtils.js @@ -0,0 +1,35 @@ +import { + canAccessWindowTop, + generateUUID, + getWindowSelf, + getWindowTop, + isSafeFrameWindow +} from '../../src/utils.js'; + +/** + * Returns the best Window object to use with ADAGIO. + * @returns {Window} window.top or window.self object + */ +export function getBestWindowForAdagio() { + return (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); +} + +/** + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. + */ +export const _ADAGIO = (function() { + const w = getBestWindowForAdagio(); + + w.ADAGIO = w.ADAGIO || {}; + w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); + 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.pbjs = '$prebid.version$'; + w.ADAGIO.windows = w.ADAGIO.windows || []; + w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); + + return w.ADAGIO; +})(); diff --git a/libraries/adkernelUtils/adkernelUtils.js b/libraries/adkernelUtils/adkernelUtils.js new file mode 100644 index 00000000000..0b2d48f3824 --- /dev/null +++ b/libraries/adkernelUtils/adkernelUtils.js @@ -0,0 +1,11 @@ +export 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; +} diff --git a/libraries/advangUtils/index.js b/libraries/advangUtils/index.js new file mode 100644 index 00000000000..f815f389ed6 --- /dev/null +++ b/libraries/advangUtils/index.js @@ -0,0 +1,228 @@ +import { deepAccess, generateUUID, isFn, parseSizesInput, parseUrl } from '../../src/utils.js'; +import { config } from '../../src/config.js'; +import { find, includes } from '../../src/polyfill.js'; + +export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; + +export function isBannerBid(bid) { + return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); +} + +export function isVideoBid(bid) { + return deepAccess(bid, 'mediaTypes.video'); +} + +export function getBannerBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; + return floorInfo?.floor || getBannerBidParam(bid, 'bidfloor'); +} + +export function getVideoBidFloor(bid) { + let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; + return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); +} + +export function isVideoBidValid(bid) { + return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); +} + +export function isBannerBidValid(bid) { + return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); +} + +export function getVideoBidParam(bid, key) { + return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); +} + +export function getBannerBidParam(bid, key) { + return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); +} + +export function isMobile() { + return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); +} + +export function isConnectedTV() { + return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); +} + +export function getDoNotTrack() { + return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; +} + +export function findAndFillParam(o, key, value) { + try { + if (typeof value === 'function') { + o[key] = value(); + } else { + o[key] = value; + } + } catch (ex) {} +} + +export 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'; +} + +export function getFirstSize(sizes) { + return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; +} + +export function parseSizes(sizes) { + return parseSizesInput(sizes).map(size => { + let [ width, height ] = size.split('x'); + return { + w: parseInt(width, 10) || undefined, + h: parseInt(height, 10) || undefined + }; + }); +} + +export function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +export function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +export function getTopWindowReferrer(bidderRequest) { + return bidderRequest?.refererInfo?.ref || ''; +} + +export function getTopWindowLocation(bidderRequest) { + return parseUrl(bidderRequest?.refererInfo?.page, {decodeSearchAsString: true}); +} + +export function getVideoTargetingParams(bid, VIDEO_TARGETING) { + 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; +} + +export function createRequestData(bid, bidderRequest, isVideo, getBidParam, getSizes, getBidFloor, BIDDER_CODE, ADAPTER_VERSION) { + let topLocation = getTopWindowLocation(bidderRequest); + let topReferrer = getTopWindowReferrer(bidderRequest); + let paramSize = getBidParam(bid, 'size'); + let sizes = []; + let coppa = config.getConfig('coppa'); + + if (typeof paramSize !== 'undefined' && paramSize != '') { + sizes = parseSizes(paramSize); + } else { + sizes = getSizes(bid); + } + + const firstSize = getFirstSize(sizes); + let floor = getBidFloor(bid) || (isVideo ? 0.5 : 0.1); + 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': Math.min(3000, bidderRequest.timeout), + '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['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 = getBidParam(bid, 'placement'); + let impType = isVideo ? { + 'video': Object.assign({ + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h, + 'mimes': DEFAULT_MIMES + }, getVideoTargetingParams(bid)) + } : { + 'banner': { + 'id': generateUUID(), + 'pos': 0, + 'w': firstSize.w, + 'h': firstSize.h + } + }; + + 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, + ...impType + }); + } + + if (coppa) { + o.regs.ext = {'coppa': 1}; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + let { gdprApplies, consentString } = bidderRequest.gdprConsent; + o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; + o.user.ext = {'consent': consentString}; + } + + return o; +} diff --git a/libraries/appnexusUtils/anUtils.js b/libraries/appnexusUtils/anUtils.js index 9b55cd5c2a4..1d04711bd0f 100644 --- a/libraries/appnexusUtils/anUtils.js +++ b/libraries/appnexusUtils/anUtils.js @@ -10,6 +10,23 @@ export function convertCamelToUnderscore(value) { }).replace(/^_/, ''); } +export const appnexusAliases = [ + { code: 'appnexusAst', gvlid: 32 }, + { code: 'emxdigital', gvlid: 183 }, + { code: 'emetriq', gvlid: 213 }, + { code: 'pagescience', gvlid: 32 }, + { code: 'gourmetads', gvlid: 32 }, + { code: 'matomy', gvlid: 32 }, + { code: 'featureforward', gvlid: 32 }, + { code: 'oftmedia', gvlid: 32 }, + { code: 'adasta', gvlid: 32 }, + { code: 'beintoo', gvlid: 618 }, + { code: 'projectagora', gvlid: 1032 }, + { code: 'stailamedia', gvlid: 32 }, + { code: 'uol', gvlid: 32 }, + { code: 'adzymic', gvlid: 723 }, +]; + /** * Creates an array of n length and fills each item with the given value */ diff --git a/libraries/audUtils/bidderUtils.js b/libraries/audUtils/bidderUtils.js new file mode 100644 index 00000000000..0d3392ec951 --- /dev/null +++ b/libraries/audUtils/bidderUtils.js @@ -0,0 +1,238 @@ +import { + deepAccess, + deepSetValue, + generateUUID, + logError +} from '../../src/utils.js'; + +// Declare native assets +const NATIVE_ASSETS = [ + { id: 1, required: 1, title: { len: 100 } }, // Title + { id: 2, required: 1, img: { type: 3, w: 300, h: 250 } }, // Main image + { id: 3, required: 0, data: { type: 1, len: 140 } }, // Body + { id: 4, required: 1, data: { type: 2 } }, // Sponsored by + { id: 5, required: 1, icon: { w: 50, h: 50 } }, // Icon + { id: 6, required: 1, data: { type: 12, len: 15 } } // Call to action +]; +// Function to get Request +export const getBannerRequest = (bidRequests, bidderRequest, ENDPOINT) => { + let request = []; + // Loop for each bid request + bidRequests.forEach(bidReq => { + let guid = generateUUID(); + const req = { + id: guid, + imp: [getImpDetails(bidReq)], + placementId: bidReq.params.placement_id, + site: getSiteDetails(bidderRequest), + user: getUserDetails(bidReq) + }; + // Fetch GPP Consent from bidderRequest + if (bidderRequest && bidderRequest.gppConsent && bidderRequest.gppConsent.gppString) { + deepSetValue(req, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(req, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.gpp) { + deepSetValue(req, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(req, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + // Fetch coppa compliance from bidderRequest + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.regs && bidderRequest.ortb2.regs.coppa) { + deepSetValue(req, 'regs.coppa', 1); + } + // Fetch uspConsent from bidderRequest + if (bidderRequest?.uspConsent) { + deepSetValue(req, 'regs.ext.us_privacy', bidderRequest.uspConsent); + } + req.MediaType = getMediaType(bidReq); + request.push(req); + }); + // Return the array of request + return { + method: 'POST', + url: ENDPOINT, + data: JSON.stringify(request), + options: { + contentType: 'application/json', + } + }; +} +// Function to get Response +export const getBannerResponse = (bidResponse, mediaType) => { + return formatResponse(bidResponse, mediaType); +} +// Function to get NATIVE Response +export const getNativeResponse = (bidResponse, bidRequest, mediaType) => { + const assets = JSON.parse(JSON.parse(bidRequest.data)[0].imp[0].native.request).assets; + return formatResponse(bidResponse, mediaType, assets); +} +// Function to format response +const formatResponse = (bidResponse, mediaType, assets) => { + let responseArray = []; + if (bidResponse) { + try { + let bidResp = deepAccess(bidResponse, 'body.seatbid', []); + if (bidResp && bidResp[0] && bidResp[0].bid) { + bidResp[0].bid.forEach(bidReq => { + let response = {}; + response.requestId = bidReq.impid; + response.cpm = bidReq.price; + response.width = bidReq.w; + response.height = bidReq.h; + response.ad = bidReq.adm; + response.meta = { + advertiserDomains: bidReq.adomain, + primaryCatId: bidReq.cat || [], + attr: bidReq.attr || [] + }; + response.creativeId = bidReq.crid; + response.netRevenue = false; + response.currency = 'USD'; + response.ttl = 300; + response.dealId = bidReq.dealId; + response.mediaType = mediaType; + if (mediaType == 'native') { + let nativeResp = JSON.parse(bidReq.adm).native; + let nativeData = { + clickUrl: nativeResp.link.url, + impressionTrackers: nativeResp.imptrackers + }; + nativeResp.assets.forEach(asst => { + let data = getNativeAssestData(asst, assets); + nativeData[data.key] = data.value; + }); + response.native = nativeData; + } + responseArray.push(response); + }); + } + } catch (e) { + logError(e); + } + } + return responseArray; +} +// Function to get imp based on Media Type +const getImpDetails = (bidReq) => { + let imp = {}; + if (bidReq) { + imp.id = bidReq.bidId; + imp.bidfloor = getFloorPrice(bidReq); + if (bidReq.mediaTypes.native) { + let assets = { assets: NATIVE_ASSETS }; + imp.native = { request: JSON.stringify(assets) }; + } else if (bidReq.mediaTypes.banner) { + imp.banner = getBannerDetails(bidReq); + } + } + return imp; +} +// Function to get banner object +const getBannerDetails = (bidReq) => { + let response = {}; + if (bidReq.mediaTypes.banner) { + // Fetch width and height from MediaTypes object, if not provided in bidReq params + if (bidReq.mediaTypes.banner.sizes && !bidReq.params.height && !bidReq.params.width) { + let sizes = bidReq.mediaTypes.banner.sizes; + if (sizes.length > 0) { + response.h = sizes[0][1]; + response.w = sizes[0][0]; + } + } else { + response.h = bidReq.params.height; + response.w = bidReq.params.width; + } + } + return response; +} +// Function to get floor price +const getFloorPrice = (bidReq) => { + let bidfloor = deepAccess(bidReq, 'params.bid_floor', 0); + return bidfloor; +} +// Function to get site object +const getSiteDetails = (bidderRequest) => { + let page = ''; + let name = ''; + if (bidderRequest && bidderRequest.refererInfo) { + page = bidderRequest.refererInfo.page; + name = bidderRequest.refererInfo.domain; + } + return {page: page, name: name}; +} +// Function to build the user object +const getUserDetails = (bidReq) => { + let user = {}; + if (bidReq && bidReq.ortb2 && bidReq.ortb2.user) { + user.id = bidReq.ortb2.user.id ? bidReq.ortb2.user.id : ''; + user.buyeruid = bidReq.ortb2.user.buyeruid ? bidReq.ortb2.user.buyeruid : ''; + user.keywords = bidReq.ortb2.user.keywords ? bidReq.ortb2.user.keywords : ''; + user.customdata = bidReq.ortb2.user.customdata ? bidReq.ortb2.user.customdata : ''; + } else { + user.id = ''; + user.buyeruid = ''; + user.keywords = ''; + user.customdata = ''; + } + return user; +} +// Function to get asset data for response +const getNativeAssestData = (params, assets) => { + let response = {}; + if (params.title) { + response.key = 'title'; + response.value = params.title.text; + } + if (params.data) { + response.key = getAssetData(params.id, assets); + response.value = params.data.value; + } + if (params.img) { + response.key = getAssetImageDataType(params.id, assets); + response.value = { + url: params.img.url, + height: params.img.h, + width: params.img.w + } + } + return response; +} +// Function to get asset data types based on id +const getAssetData = (paramId, asset) => { + let resp = ''; + for (let i = 0; i < asset.length; i++) { + if (asset[i].id == paramId) { + switch (asset[i].data.type) { + case 1 : resp = 'sponsored'; + break; + case 2 : resp = 'desc'; + break; + case 12 : resp = 'cta'; + break; + } + } + } + return resp; +} +// Function to get image type based on the id +const getAssetImageDataType = (paramId, asset) => { + let resp = ''; + for (let i = 0; i < asset.length; i++) { + if (asset[i].id == paramId) { + switch (asset[i].img.type) { + case 1 : resp = 'icon'; + break; + case 3 : resp = 'image'; + break; + } + } + } + return resp; +} +// Function to get Media Type +const getMediaType = (bidReq) => { + if (bidReq.mediaTypes.native) { + return 'native'; + } else if (bidReq.mediaTypes.banner) { + return 'banner'; + } +} diff --git a/libraries/autoplayDetection/autoplay.js b/libraries/autoplayDetection/autoplay.js index b598e46cbd1..3ca4c4a8d11 100644 --- a/libraries/autoplayDetection/autoplay.js +++ b/libraries/autoplayDetection/autoplay.js @@ -1,6 +1,7 @@ let autoplayEnabled = null; /** + * DEVELOPER WARNING: IMPORTING THIS LIBRARY MAY MAKE YOUR ADAPTER NO LONGER COMPATIBLE WITH APP PUBLISHERS USING WKWEBVIEW * Note: this function returns true if detection is not done yet. This is by design: if autoplay is not allowed, * the call to video.play() will fail immediately, otherwise it may not terminate. * @returns true if autoplay is not forbidden @@ -21,6 +22,12 @@ const autoplayVideoUrl = 'data:video/mp4;base64,AAAAIGZ0eXBpc29tAAACAGlzb21pc28yYXZjMW1wNDEAAAAIZnJlZQAAADxtZGF0AAAAMGWIhAAV//73ye/Apuvb3rW/k89I/Cy3PsIqP39atohOSV14BYa1heKCYgALQC5K4QAAAwZtb292AAAAbG12aGQAAAAAAAAAAAAAAAAAAAPoAAAD6AABAAABAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACAAACMHRyYWsAAABcdGtoZAAAAAMAAAAAAAAAAAAAAAEAAAAAAAAD6AAAAAAAAAAAAAAAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAEAAAAAAAAAAAAAAAAAAEAAAAAAoAAAAFoAAAAAACRlZHRzAAAAHGVsc3QAAAAAAAAAAQAAA+gAAAAAAAEAAAAAAahtZGlhAAAAIG1kaGQAAAAAAAAAAAAAAAAAAEAAAABAAFXEAAAAAAAtaGRscgAAAAAAAAAAdmlkZQAAAAAAAAAAAAAAAFZpZGVvSGFuZGxlcgAAAAFTbWluZgAAABR2bWhkAAAAAQAAAAAAAAAAAAAAJGRpbmYAAAAcZHJlZgAAAAAAAAABAAAADHVybCAAAAABAAABE3N0YmwAAACvc3RzZAAAAAAAAAABAAAAn2F2YzEAAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAoABaAEgAAABIAAAAAAAAAAEVTGF2YzYwLjMxLjEwMiBsaWJ4MjY0AAAAAAAAAAAAAAAY//8AAAA1YXZjQwFkAAr/4QAYZ2QACqzZQo35IQAAAwABAAADAAIPEiWWAQAGaOvjyyLA/fj4AAAAABRidHJ0AAAAAAAAAaAAAAGgAAAAGHN0dHMAAAAAAAAAAQAAAAEAAEAAAAAAHHN0c2MAAAAAAAAAAQAAAAEAAAABAAAAAQAAABRzdHN6AAAAAAAAADQAAAABAAAAFHN0Y28AAAAAAAAAAQAAADAAAABidWR0YQAAAFptZXRhAAAAAAAAACFoZGxyAAAAAAAAAABtZGlyYXBwbAAAAAAAAAAAAAAAAC1pbHN0AAAAJal0b28AAAAdZGF0YQAAAAEAAAAATGF2ZjYwLjE2LjEwMA=='; function startDetection() { + const version = navigator.userAgent.match(/iPhone OS (\d+)_(\d+)/) + if (version !== null && parseInt(version[1]) < 17 && !navigator.userAgent.includes('Safari')) { + // skip autodetection on iOS 16 WebView + return + } + // we create an HTMLVideoElement muted and not displayed in which we try to play a one frame video const videoElement = document.createElement('video'); videoElement.src = autoplayVideoUrl; @@ -31,7 +38,8 @@ function startDetection() { .play() .then(() => { autoplayEnabled = true; - videoElement.pause(); + // if the video is played on a WebView with playsinline = false, this stops the video, to prevent it from being displayed fullscreen + videoElement.src = ''; }) .catch(() => { autoplayEnabled = false; diff --git a/libraries/biddoInvamiaUtils/index.js b/libraries/biddoInvamiaUtils/index.js new file mode 100644 index 00000000000..0d7c4b1b683 --- /dev/null +++ b/libraries/biddoInvamiaUtils/index.js @@ -0,0 +1,70 @@ +/** + * Helper function to build request payload for banner ads. + * @param {Object} bidRequest - The bid request object. + * @param {string} endpointUrl - The endpoint URL specific to the bidder. + * @returns {Array} An array of server requests. + */ +export function buildBannerRequests(bidRequest, endpointUrl) { + const serverRequests = []; + 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((key) => `${key}=${encodeURIComponent(payload[key])}`) + .join('&'); + + serverRequests.push({ + method: 'GET', + url: endpointUrl, + data: payloadString, + bidderRequest: bidRequest, + }); + }); + + return serverRequests; +} + +/** + * Helper function to interpret server response for banner ads. + * @param {Object} serverResponse - The server response object. + * @param {Object} bidderRequest - The matched bid request for this response. + * @returns {Array} An array of bid responses. + */ +export function interpretBannerResponse(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; +} diff --git a/libraries/braveUtils/buildAndInterpret.js b/libraries/braveUtils/buildAndInterpret.js new file mode 100644 index 00000000000..66cd63896f7 --- /dev/null +++ b/libraries/braveUtils/buildAndInterpret.js @@ -0,0 +1,78 @@ +import { isEmpty, parseUrl } from '../../src/utils.js'; +import {config} from '../../src/config.js'; +import { createNativeRequest, createBannerRequest, createVideoRequest } from './index.js'; +import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; + +export const buildRequests = (validBidRequests, bidderRequest, endpointURL, defaultCur) => { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + if (!validBidRequests.length || !bidderRequest) return []; + + const endpoint = endpointURL.replace('hash', validBidRequests[0].params.placementId); + const imp = validBidRequests.map((br) => { + const 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; + }); + + const page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const data = { + id: bidderRequest.bidderRequestId, + cur: [defaultCur], + device: { w: screen.width, h: screen.height, language: navigator.language?.split('-')[0], ua: navigator.userAgent }, + site: { domain: parseUrl(page).hostname, page: page }, + tmax: bidderRequest.timeout, + imp, + }; + + if (bidderRequest.refererInfo.ref) data.site.ref = bidderRequest.refererInfo.ref; + if (bidderRequest.gdprConsent) { + data.regs = { ext: { gdpr: bidderRequest.gdprConsent.gdprApplies ? 1 : 0 } }; + data.user = { ext: { consent: bidderRequest.gdprConsent.consentString || '' } }; + } + if (bidderRequest.uspConsent) data.regs.ext.us_privacy = bidderRequest.uspConsent; + if (config.getConfig('coppa')) data.regs.coppa = 1; + if (validBidRequests[0].schain) data.source = { ext: { schain: validBidRequests[0].schain } }; + + return { method: 'POST', url: endpoint, data }; +}; + +export const interpretResponse = (serverResponse, defaultCur, parseNative) => { + if (!serverResponse || isEmpty(serverResponse.body)) return []; + + let bids = []; + serverResponse.body.seatbid.forEach(response => { + response.bid.forEach(bid => { + const mediaType = bid.ext?.mediaType || 'banner'; + + const bidObj = { + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + ttl: 1200, + currency: defaultCur, + netRevenue: true, + creativeId: bid.crid, + dealId: bid.dealid || null, + 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; +}; diff --git a/libraries/braveUtils/index.js b/libraries/braveUtils/index.js new file mode 100644 index 00000000000..5756e09ae5c --- /dev/null +++ b/libraries/braveUtils/index.js @@ -0,0 +1,95 @@ +import { NATIVE_ASSETS, NATIVE_ASSETS_IDS } from './nativeAssets.js'; + +/** + * Builds a native request object based on the bid request + * @param {object} br - The bid request + * @returns {object} The native request object + */ +export function createNativeRequest(br) { + let impObject = { + ver: '1.2', + assets: [] + }; + + Object.keys(br.mediaTypes.native).forEach((key) => { + 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; +} + +/** + * Builds a banner request object based on the bid request + * @param {object} br - The bid request + * @returns {object} The banner request object + */ +export function createBannerRequest(br) { + let size = br.mediaTypes.banner.sizes?.[0] || [300, 250]; + return { id: br.transactionId, w: size[0], h: size[1] }; +} + +/** + * Builds a video request object based on the bid request + * @param {object} br - The bid request + * @returns {object} The video request object + */ +export function createVideoRequest(br) { + let videoObj = { id: br.transactionId }; + const supportedParams = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'minbitrate', 'maxbitrate', 'api', 'linearity']; + + supportedParams.forEach((param) => { + if (br.mediaTypes.video[param] !== undefined) { + videoObj[param] = br.mediaTypes.video[param]; + } + }); + + const playerSize = br.mediaTypes.video.playerSize; + if (playerSize) { + videoObj.w = Array.isArray(playerSize[0]) ? playerSize[0][0] : playerSize[0]; + videoObj.h = Array.isArray(playerSize[0]) ? playerSize[0][1] : playerSize[1]; + } else { + videoObj.w = 640; + videoObj.h = 480; + } + + return videoObj; +} + +/** + * Parses the native ad response + * @param {object} adm - The native ad response + * @returns {object} Parsed native ad object + */ +export function parseNative(adm) { + let bid = { + clickUrl: adm.native.link?.url, + impressionTrackers: adm.native.imptrackers || [], + clickTrackers: adm.native.link?.clicktrackers || [], + jstracker: adm.native.jstracker || [] + }; + adm.native.assets.forEach((asset) => { + const kind = NATIVE_ASSETS_IDS[asset.id]; + const 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; +} diff --git a/libraries/braveUtils/nativeAssets.js b/libraries/braveUtils/nativeAssets.js new file mode 100644 index 00000000000..07de1264d0c --- /dev/null +++ b/libraries/braveUtils/nativeAssets.js @@ -0,0 +1,23 @@ +/** + * IDs and asset types for native ad assets. + */ +export const NATIVE_ASSETS_IDS = { + 1: 'title', + 2: 'icon', + 3: 'image', + 4: 'body', + 5: 'sponsoredBy', + 6: 'cta' +}; + +/** + * Native assets definition for mapping purposes. + */ +export 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' } +}; diff --git a/libraries/connectionInfo/connectionUtils.js b/libraries/connectionInfo/connectionUtils.js new file mode 100644 index 00000000000..29fed27b91d --- /dev/null +++ b/libraries/connectionInfo/connectionUtils.js @@ -0,0 +1,33 @@ +/** + * Returns the type of connection. + * + * @returns {number} - Type of connection. + */ +export function getConnectionType() { + const connection = navigator.connection || navigator.webkitConnection; + if (!connection) { + return 0; + } + switch (connection.type) { + case 'ethernet': + return 1; + case 'wifi': + return 2; + case 'wimax': + return 6; + default: + switch (connection.effectiveType) { + case 'slow-2g': + case '2g': + return 4; + case '3g': + return 5; + case '4g': + return 6; + case '5g': + return 7; + default: + return connection.type == 'cellular' ? 3 : 0; + } + } +} diff --git a/libraries/consentManagement/cmUtils.js b/libraries/consentManagement/cmUtils.js new file mode 100644 index 00000000000..f4dbd81f493 --- /dev/null +++ b/libraries/consentManagement/cmUtils.js @@ -0,0 +1,38 @@ +import {timedAuctionHook} from '../../src/utils/perfMetrics.js'; +import {logError, logInfo, logWarn} from '../../src/utils.js'; + +export function consentManagementHook(name, getConsent, loadConsentData) { + function loadIfMissing(cb) { + if (getConsent()) { + logInfo('User consent information already known. Pulling internally stored information...'); + // eslint-disable-next-line standard/no-callback-literal + cb(false); + } else { + loadConsentData(cb); + } + } + + return timedAuctionHook(name, function requestBidsHook(fn, reqBidsConfigObj) { + loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { + if (errMsg) { + let log = logWarn; + if (shouldCancelAuction) { + log = logError; + errMsg = `${errMsg} Canceling auction as per consentManagement config.`; + } + log(errMsg, ...extraArgs); + } + + if (shouldCancelAuction) { + fn.stopTiming(); + if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { + reqBidsConfigObj.bidsBackHandler(); + } else { + logError('Error executing bidsBackHandler'); + } + } else { + fn.call(this, reqBidsConfigObj); + } + }); + }); +} diff --git a/libraries/currencyUtils/floor.js b/libraries/currencyUtils/floor.js new file mode 100644 index 00000000000..ee41bad01da --- /dev/null +++ b/libraries/currencyUtils/floor.js @@ -0,0 +1,23 @@ +import * as utils from '../../src/utils.js'; + +/** + * get BidFloor + * @param {*} bid + * @returns + */ +export 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; + } +} diff --git a/libraries/deepintentUtils/index.js b/libraries/deepintentUtils/index.js new file mode 100644 index 00000000000..5abe2d1d061 --- /dev/null +++ b/libraries/deepintentUtils/index.js @@ -0,0 +1,42 @@ +import { isInteger } from '../../src/utils.js'; + +export const COMMON_ORTB_VIDEO_PARAMS = { + 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), + 'minduration': (value) => isInteger(value), + 'maxduration': (value) => isInteger(value), + 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), + 'w': (value) => isInteger(value), + 'h': (value) => isInteger(value), + 'startdelay': (value) => isInteger(value), + 'linearity': (value) => [1, 2].indexOf(value) !== -1, + 'skip': (value) => [0, 1].indexOf(value) !== -1, + 'skipmin': (value) => isInteger(value), + 'skipafter': (value) => isInteger(value), + 'sequence': (value) => isInteger(value), + 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), + 'maxextended': (value) => isInteger(value), + 'minbitrate': (value) => isInteger(value), + 'maxbitrate': (value) => isInteger(value), + 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, + 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), + 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) +}; + +export function formatResponse(bid) { + return { + requestId: bid && bid.impid ? bid.impid : undefined, + cpm: bid && bid.price ? bid.price : 0.0, + 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', + ttl: 300, + dealId: bid && bid.dealId ? bid.dealId : undefined + } +} diff --git a/libraries/dfpUtils/dfpUtils.js b/libraries/dfpUtils/dfpUtils.js new file mode 100644 index 00000000000..4b957eb4999 --- /dev/null +++ b/libraries/dfpUtils/dfpUtils.js @@ -0,0 +1,26 @@ +import {gdprDataHandler} from '../../src/consentHandler.js'; + +/** Safe defaults which work on pretty much all video calls. */ +export const DEFAULT_DFP_PARAMS = { + env: 'vp', + gdfp_req: 1, + output: 'vast', + unviewed_position_start: 1, +} + +export const DFP_ENDPOINT = { + protocol: 'https', + host: 'securepubads.g.doubleclick.net', + pathname: '/gampad/ads' +} + +export function gdprParams() { + const gdprConsent = gdprDataHandler.getConsentData(); + const params = {}; + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { params.gdpr = Number(gdprConsent.gdprApplies); } + if (gdprConsent.consentString) { params.gdpr_consent = gdprConsent.consentString; } + if (gdprConsent.addtlConsent) { params.addtl_consent = gdprConsent.addtlConsent; } + } + return params; +} diff --git a/libraries/dspxUtils/bidderUtils.js b/libraries/dspxUtils/bidderUtils.js new file mode 100644 index 00000000000..612a20f6865 --- /dev/null +++ b/libraries/dspxUtils/bidderUtils.js @@ -0,0 +1,390 @@ +import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {deepAccess, isArray, isEmptyStr, isFn, logError} from '../../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ +/** + * Adds userIds to payload + * + * @param bidRequest + * @param payload + */ +export function fillUsersIds(bidRequest, payload) { + if (bidRequest.hasOwnProperty('userId')) { + let didMapping = { + did_netid: 'userId.netId', + did_id5: 'userId.id5id.uid', + did_id5_linktype: 'userId.id5id.ext.linkType', + did_uid2: 'userId.uid2', + did_sharedid: 'userId.sharedid', + did_pubcid: 'userId.pubcid', + did_uqid: 'userId.utiq', + did_cruid: 'userId.criteoid', + did_euid: 'userId.euid', + // did_tdid: 'unifiedId', + did_tdid: 'userId.tdid', + did_ppuid: function() { + let path = 'userId.pubProvidedId'; + let value = deepAccess(bidRequest, path); + if (isArray(value)) { + for (const rec of value) { + if (rec.uids && rec.uids.length > 0) { + for (let i = 0; i < rec.uids.length; i++) { + if ('id' in rec.uids[i] && deepAccess(rec.uids[i], 'ext.stype') === 'ppuid') { + return (rec.uids[i].atype ?? '') + ':' + rec.source + ':' + rec.uids[i].id; + } + } + } + } + } + return undefined; + }, + did_cpubcid: 'crumbs.pubcid' + }; + for (let paramName in didMapping) { + let path = didMapping[paramName]; + + // handle function + if (typeof path == 'function') { + let value = path(paramName); + if (value) { + payload[paramName] = value; + } + continue; + } + // direct access + let value = deepAccess(bidRequest, path); + if (typeof value == 'string' || typeof value == 'number') { + payload[paramName] = value; + } else if (typeof value == 'object') { + // trying to find string ID value + if (typeof deepAccess(bidRequest, path + '.id') == 'string') { + payload[paramName] = deepAccess(bidRequest, path + '.id'); + } else { + if (Object.keys(value).length > 0) { + logError(`WARNING: fillUserIds had to use first key in user object to get value for bid.userId key: ${path}.`); + payload[paramName] = value[Object.keys(value)[0]]; + } + } + } + } + } +} + +export function appendToUrl(url, what) { + if (!what) { + return url; + } + return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; +} + +export 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) + '=' + encodeURIComponent(v)); + } + } + return str.filter(n => n).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 + */ +export function isBannerRequest(bid) { + return bid.mediaType === 'banner' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); +} + +/** + * Check if it's a video bid request + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {boolean} True if it's a video bid + */ +export function isVideoRequest(bid) { + return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); +} + +/** + * Get video sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +export function getVideoSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); +} + +/** + * Get video context + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} + */ +export function getVideoContext(bid) { + return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; +} + +/** + * Get banner sizes + * + * @param {BidRequest} bid - Bid request generated from ad slots + * @returns {object} True if it's a video bid + */ +export function getBannerSizes(bid) { + return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); +} + +/** + * Parse size + * @param size + * @returns {object} sizeObj + */ +export 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 }[]} + */ +export 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]) +} + +/** + * Get MediaInfo object for server request + * + * @param mediaTypesInfo + * @returns {*} + */ +export function convertMediaInfoForRequest(mediaTypesInfo) { + let requestData = {}; + Object.keys(mediaTypesInfo).forEach(mediaType => { + requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { + return size.width + 'x' + size.height; + }).join(','); + }); + return requestData; +} + +/** + * Get media types info + * + * @param bid + */ +export function getMediaTypesInfo(bid) { + let mediaTypesInfo = {}; + + if (bid.mediaTypes) { + Object.keys(bid.mediaTypes).forEach(mediaType => { + if (mediaType === BANNER) { + mediaTypesInfo[mediaType] = getBannerSizes(bid); + } + if (mediaType === VIDEO) { + mediaTypesInfo[mediaType] = getVideoSizes(bid); + } + }); + } else { + mediaTypesInfo[BANNER] = getBannerSizes(bid); + } + return mediaTypesInfo; +} + +/** + * Get Bid Floor + * @param bid + * @returns {number|*} + */ +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidfloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + return bidFloor?.floor; + } catch (_) { + return 0 + } +} + +/** + * Convert site.content to string + * @param content + */ +export function siteContentToString(content) { + if (!content) { + return ''; + } + let stringKeys = ['id', 'title', 'series', 'season', 'artist', 'genre', 'isrc', 'url', 'keywords']; + let intKeys = ['episode', 'context', 'livestream']; + let arrKeys = ['cat']; + let retArr = []; + arrKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && Array.isArray(val)) { + retArr.push(k + ':' + val.join('|')); + } + }); + intKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'number') { + retArr.push(k + ':' + val); + } + }); + stringKeys.forEach(k => { + let val = deepAccess(content, k); + if (val && typeof val === 'string') { + retArr.push(k + ':' + encodeURIComponent(val)); + } + }); + return retArr.join(','); +} + +/** + * Assigns multiple values to the specified keys on an object if the values are not undefined. + * @param {Object} target - The object to which the values will be assigned. + * @param {Object} values - An object containing key-value pairs to be assigned. + */ +export function assignDefinedValues(target, values) { + for (const key in values) { + if (values[key] !== undefined) { + target[key] = values[key]; + } + } +} + +/** + * Extracts user segments/topics from the bid request object + * @param {Object} bid - The bid request object + * @returns {{segclass: *, segtax: *, segments: *}|undefined} - User segments/topics or undefined if not found + */ +export function extractUserSegments(bid) { + const userData = deepAccess(bid, 'ortb2.user.data') || []; + for (const dataObj of userData) { + if (dataObj.segment && isArray(dataObj.segment) && dataObj.segment.length > 0) { + const segments = dataObj.segment + .filter(seg => seg.id && !isEmptyStr(seg.id) && isFinite(seg.id)) + .map(seg => Number(seg.id)); + if (segments.length > 0) { + return { + segtax: deepAccess(dataObj, 'ext.segtax'), + segclass: deepAccess(dataObj, 'ext.segclass'), + segments: segments.join(',') + }; + } + } + } + return undefined; +} + +export function handleSyncUrls(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; +} + +export function interpretResponse(serverResponse, bidRequest, rendererFunc) { + 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, + type: response.type, + ttl: 60, + meta: { + advertiserDomains: response.adomain || [] + } + }; + + if (response.vastUrl) { + bidResponse.vastUrl = response.vastUrl; + bidResponse.mediaType = 'video'; + } + if (response.vastXml) { + bidResponse.vastXml = response.vastXml; + bidResponse.mediaType = 'video'; + } + if (response.renderer) { + bidResponse.renderer = rendererFunc(bidRequest, response); + } + + if (response.videoCacheKey) { + bidResponse.videoCacheKey = response.videoCacheKey; + } + + if (response.adTag) { + bidResponse.ad = response.adTag; + } + + if (response.bid_appendix) { + Object.keys(response.bid_appendix).forEach(fieldName => { + bidResponse[fieldName] = response.bid_appendix[fieldName]; + }); + } + + bidResponses.push(bidResponse); + } + return bidResponses; +} diff --git a/libraries/equativUtils/equativUtils.js b/libraries/equativUtils/equativUtils.js new file mode 100644 index 00000000000..bdcbdad2f33 --- /dev/null +++ b/libraries/equativUtils/equativUtils.js @@ -0,0 +1,30 @@ +import { VIDEO } from '../../src/mediaTypes.js'; +import { deepAccess, isFn } from '../../src/utils.js'; + +const DEFAULT_FLOOR = 0.0; + +/** + * Get floors from Prebid Price Floors module + * + * @param {object} bid Bid request object + * @param {string} currency Ad server currency + * @param {string} mediaType Bid media type + * @return {number} Floor price + */ +export function getBidFloor (bid, currency, mediaType) { + const floors = []; + + if (isFn(bid.getFloor)) { + (deepAccess(bid, `mediaTypes.${mediaType}.${mediaType === VIDEO ? 'playerSize' : 'sizes'}`) || []).forEach(size => { + const floor = bid.getFloor({ + currency: currency || 'USD', + mediaType, + size + }).floor; + + floors.push(!isNaN(floor) ? floor : DEFAULT_FLOOR); + }); + } + + return floors.length ? Math.min(...floors) : DEFAULT_FLOOR; +} diff --git a/libraries/fpdUtils/deviceInfo.js b/libraries/fpdUtils/deviceInfo.js new file mode 100644 index 00000000000..fd2c4bf47d6 --- /dev/null +++ b/libraries/fpdUtils/deviceInfo.js @@ -0,0 +1,58 @@ +import * as utils from '../../src/utils.js'; + +/** + * get device + * @return {boolean} + */ +export function getDevice() { + let check = false; + (function (a) { + let reg1 = new RegExp( + [ + '(android|bbd+|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|android|ipad|playbook|silk', + ].join(''), + 'i' + ); + let reg2 = new RegExp( + [ + '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-', + ].join(''), + 'i' + ); + if (reg1.test(a) || reg2.test(a.substr(0, 4))) { + check = true; + } + })(navigator.userAgent || navigator.vendor || window.opera); + return check; +} + +/** + * get screen size + * + * @returns {Array} eg: "['widthxheight']" + */ +export function getScreenSize() { + return utils.parseSizesInput([window.screen.width, window.screen.height]); +} diff --git a/libraries/fpdUtils/pageInfo.js b/libraries/fpdUtils/pageInfo.js new file mode 100644 index 00000000000..dcf172d96c8 --- /dev/null +++ b/libraries/fpdUtils/pageInfo.js @@ -0,0 +1,73 @@ +import * as utils from '../../src/utils.js'; + +/** + * get page title + * @returns {string} + */ +export function getPageTitle(win = window) { + try { + const ogTitle = win.top.document.querySelector('meta[property="og:title"]'); + return win.top.document.title || (ogTitle && ogTitle.content) || ''; + } catch (e) { + const ogTitle = document.querySelector('meta[property="og:title"]'); + return document.title || (ogTitle && ogTitle.content) || ''; + } +} + +/** + * get page description + * @returns {string} + */ +export function getPageDescription(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="description"]') || + win.top.document.querySelector('meta[property="og:description"]') + } catch (e) { + element = document.querySelector('meta[name="description"]') || + document.querySelector('meta[property="og:description"]') + } + + return (element && element.content) || ''; +} + +/** + * get page keywords + * @returns {string} + */ +export function getPageKeywords(win = window) { + let element; + + try { + element = win.top.document.querySelector('meta[name="keywords"]'); + } catch (e) { + element = document.querySelector('meta[name="keywords"]'); + } + + return (element && element.content) || ''; +} + +/** + * get connection downlink + * @returns {number} + */ +export function getConnectionDownLink(win = window) { + const nav = win.navigator || {}; + return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; +} + +/** + * @param bidRequest + * @param bidderRequest + * @returns {string} + */ +export function getReferrer(bidRequest = {}, bidderRequest = {}) { + let pageUrl; + if (bidRequest.params && bidRequest.params.referrer) { + pageUrl = bidRequest.params.referrer; + } else { + pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page'); + } + return pageUrl; +} diff --git a/libraries/gptUtils/gptUtils.js b/libraries/gptUtils/gptUtils.js index 950f28c618f..923d207c0d9 100644 --- a/libraries/gptUtils/gptUtils.js +++ b/libraries/gptUtils/gptUtils.js @@ -1,5 +1,6 @@ +import { CLIENT_SECTIONS } from '../../src/fpd/oneClient.js'; import {find} from '../../src/polyfill.js'; -import {compareCodeAndSlot, isGptPubadsDefined} from '../../src/utils.js'; +import {compareCodeAndSlot, deepAccess, isGptPubadsDefined, uniques} from '../../src/utils.js'; /** * Returns filter function to match adUnitCode in slot @@ -10,6 +11,18 @@ export function isSlotMatchingAdUnitCode(adUnitCode) { return (slot) => compareCodeAndSlot(slot, adUnitCode); } +/** + * @summary Export a k-v pair to GAM + */ +export function setKeyValue(key, value) { + if (!key || typeof key !== 'string') return false; + window.googletag = window.googletag || {cmd: []}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(() => { + window.googletag.pubads().setTargeting(key, value); + }); +} + /** * @summary Uses the adUnit's code in order to find a matching gpt slot object on the page */ @@ -35,3 +48,88 @@ export function getGptSlotInfoForAdUnitCode(adUnitCode) { } return {}; } + +export const taxonomies = ['IAB_AUDIENCE_1_1', 'IAB_CONTENT_2_2']; + +export function getSignals(fpd) { + const signals = Object.entries({ + [taxonomies[0]]: getSegments(fpd, ['user.data'], 4), + [taxonomies[1]]: getSegments(fpd, CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) + }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) + .filter(ob => ob); + + return signals; +} + +export function getSegments(fpd, sections, segtax) { + return sections + .flatMap(section => deepAccess(fpd, section) || []) + .filter(datum => datum.ext?.segtax === segtax) + .flatMap(datum => datum.segment?.map(seg => seg.id)) + .filter(ob => ob) + .filter(uniques) +} + +/** + * Add an event listener on the given GAM event. + * If GPT Pubads isn't defined, window.googletag is set to a new object. + * @param {String} event + * @param {Function} callback + */ +export function subscribeToGamEvent(event, callback) { + const register = () => window.googletag.pubads().addEventListener(event, callback); + if (isGptPubadsDefined()) { + register(); + return; + } + window.googletag = window.googletag || {}; + window.googletag.cmd = window.googletag.cmd || []; + window.googletag.cmd.push(register); +} + +/** + * @typedef {Object} Slot + * @property {function(String): (String|null)} get + * @property {function(): String} getAdUnitPath + * @property {function(): String[]} getAttributeKeys + * @property {function(): String[]} getCategoryExclusions + * @property {function(String): String} getSlotElementId + * @property {function(): String[]} getTargeting + * @property {function(): String[]} getTargetingKeys + * @see {@link https://developers.google.com/publisher-tag/reference#googletag.Slot GPT official docs} + */ + +/** + * @typedef {Object} SlotRenderEndedEvent + * @property {(String|null)} advertiserId + * @property {(String|null)} campaignId + * @property {(String[]|null)} companyIds + * @property {(Number|null)} creativeId + * @property {(Number|null)} creativeTemplateId + * @property {(Boolean)} isBackfill + * @property {(Boolean)} isEmpty + * @property {(Number[]|null)} labelIds + * @property {(Number|null)} lineItemId + * @property {(String)} serviceName + * @property {(string|Number[]|null)} size + * @property {(Slot)} slot + * @property {(Boolean)} slotContentChanged + * @property {(Number|null)} sourceAgnosticCreativeId + * @property {(Number|null)} sourceAgnosticLineItemId + * @property {(Number[]|null)} yieldGroupIds + * @see {@link https://developers.google.com/publisher-tag/reference#googletag.events.SlotRenderEndedEvent GPT official docs} + */ + +/** + * @callback SlotRenderEndedEventCallback + * @param {SlotRenderEndedEvent} event + * @returns {void} + */ + +/** + * Add an event listener on the GAM event 'slotRenderEnded'. + * @param {SlotRenderEndedEventCallback} callback + */ +export function subscribeToGamSlotRenderEndedEvent(callback) { + subscribeToGamEvent('slotRenderEnded', callback) +} diff --git a/libraries/intentIqConstants/intentIqConstants.js b/libraries/intentIqConstants/intentIqConstants.js new file mode 100644 index 00000000000..ed9856fc213 --- /dev/null +++ b/libraries/intentIqConstants/intentIqConstants.js @@ -0,0 +1,10 @@ +export const FIRST_PARTY_KEY = '_iiq_fdata'; + +export const WITH_IIQ = 'A'; +export const WITHOUT_IIQ = 'B'; +export const NOT_YET_DEFINED = 'U'; +export const OPT_OUT = 'O'; +export const BLACK_LIST = 'L'; +export const CLIENT_HINTS_KEY = '_iiq_ch'; +export const EMPTY = 'EMPTY' +export const VERSION = 0.24 diff --git a/libraries/intentIqUtils/detectBrowserUtils.js b/libraries/intentIqUtils/detectBrowserUtils.js new file mode 100644 index 00000000000..c7004c77ae9 --- /dev/null +++ b/libraries/intentIqUtils/detectBrowserUtils.js @@ -0,0 +1,82 @@ +import { logError } from '../../src/utils.js'; + +/** + * Detects the browser using either userAgent or userAgentData + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowser() { + try { + if (navigator.userAgent) { + return detectBrowserFromUserAgent(navigator.userAgent); + } else if (navigator.userAgentData) { + return detectBrowserFromUserAgentData(navigator.userAgentData); + } + } catch (error) { + logError('Error detecting browser:', error); + } + return 'unknown'; +} + +/** + * Detects the browser from the user agent string + * @param {string} userAgent - The user agent string from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgent(userAgent) { + const browserRegexPatterns = { + opera: /Opera|OPR/, + edge: /Edg/, + chrome: /Chrome|CriOS/, + safari: /Safari/, + firefox: /Firefox/, + ie: /MSIE|Trident/, + }; + + // Check for Edge first + if (browserRegexPatterns.edge.test(userAgent)) { + return 'edge'; + } + + // Check for Opera next + if (browserRegexPatterns.opera.test(userAgent)) { + return 'opera'; + } + + // Check for Chrome first to avoid confusion with Safari + if (browserRegexPatterns.chrome.test(userAgent)) { + return 'chrome'; + } + + // Now we can safely check for Safari + if (browserRegexPatterns.safari.test(userAgent) && !browserRegexPatterns.chrome.test(userAgent)) { + return 'safari'; + } + + // Check other browsers + for (const browser in browserRegexPatterns) { + if (browserRegexPatterns[browser].test(userAgent)) { + return browser; + } + } + + return 'unknown'; +} + +/** + * Detects the browser from the NavigatorUAData object + * @param {NavigatorUAData} userAgentData - The user agent data object from the browser + * @return {string} The name of the detected browser or 'unknown' if unable to detect + */ +export function detectBrowserFromUserAgentData(userAgentData) { + const brandNames = userAgentData.brands.map(brand => brand.brand); + + if (brandNames.includes('Microsoft Edge')) { + return 'edge'; + } else if (brandNames.includes('Opera')) { + return 'opera'; + } else if (brandNames.some(brand => brand === 'Chromium' || brand === 'Google Chrome')) { + return 'chrome'; + } + + return 'unknown'; +} diff --git a/libraries/intentIqUtils/getGppValue.js b/libraries/intentIqUtils/getGppValue.js new file mode 100644 index 00000000000..9c538b4f753 --- /dev/null +++ b/libraries/intentIqUtils/getGppValue.js @@ -0,0 +1,16 @@ +import {gppDataHandler} from '../../src/consentHandler.js'; + +/** + * Retrieves the GPP string value and additional GPP-related information. + * This function extracts the GPP data, encodes it, and determines specific GPP flags such as GPI and applicable sections. + * @return {Object} An object containing: + * - `gppString` (string): The encoded GPP string value. + * - `gpi` (number): An indicator representing whether GPP consent is available (0 if available, 1 if not). + */ +export function getGppValue() { + const gppData = gppDataHandler.getConsentData(); + const gppString = gppData?.gppString || ''; + const gpi = gppString ? 0 : 1; + + return { gppString, gpi }; +} diff --git a/libraries/intentIqUtils/getRefferer.js b/libraries/intentIqUtils/getRefferer.js new file mode 100644 index 00000000000..39fde70ac24 --- /dev/null +++ b/libraries/intentIqUtils/getRefferer.js @@ -0,0 +1,64 @@ +import { getWindowTop, logError, getWindowLocation, getWindowSelf } from '../../src/utils.js'; + +/** + * Determines if the script is running inside an iframe and retrieves the URL. + * @return {string} The encoded vrref value representing the relevant URL. + */ +export function getReferrer() { + try { + if (getWindowSelf() === getWindowTop()) { + return encodeURIComponent(getWindowLocation().href); + } else { + return encodeURIComponent(getWindowTop().location.href); + } + } catch (error) { + logError(`Error accessing location: ${error}`); + return ''; + } +} + +/** + * Appends `vrref` and `fui` parameters to the provided URL. + * If the referrer URL is available, it appends `vrref` with the relevant referrer value based on the domain. + * Otherwise, it appends `fui=1`. If a domain name is provided, it may also append `vrref` with the domain. + * @param {string} url - The URL to append parameters to. + * @param {string} domainName - The domain name used to determine the relevant referrer. + * @return {string} The modified URL with appended `vrref` or `fui` parameters. + */ +export function appendVrrefAndFui(url, domainName) { + const fullUrl = getReferrer(); + if (fullUrl) { + return (url += '&vrref=' + getRelevantRefferer(domainName, fullUrl)); + } + url += '&fui=1'; // Full Url Issue + url += '&vrref=' + encodeURIComponent(domainName || ''); + return url; +} + +/** + * Get the relevant referrer based on full URL and domain + * @param {string} domainName The domain name to compare + * @param {string} fullUrl The full URL to analyze + * @return {string} The relevant referrer + */ +export function getRelevantRefferer(domainName, fullUrl) { + if (domainName && isDomainIncluded(fullUrl, domainName)) { + return fullUrl; + } + return domainName ? encodeURIComponent(domainName) : fullUrl; +} + +/** + * Checks if the provided domain name is included in the full URL. + * @param {string} fullUrl - The full URL to check. + * @param {string} domainName - The domain name to search for within the URL. + * @return {boolean} `True` if the domain name is found in the URL, `false` otherwise. + */ +export function isDomainIncluded(fullUrl, domainName) { + try { + return fullUrl.includes(domainName); + } catch (error) { + logError(`Invalid URL provided: ${error}`); + return false; + } +} diff --git a/libraries/interpretResponseUtils/index.js b/libraries/interpretResponseUtils/index.js new file mode 100644 index 00000000000..6d081e4c272 --- /dev/null +++ b/libraries/interpretResponseUtils/index.js @@ -0,0 +1,22 @@ +import {logError} from '../../src/utils.js'; + +export function interpretResponseUtil(serverResponse, {bidderRequest}, eachBidCallback) { + const bids = []; + if (!serverResponse.body || serverResponse.body.error) { + let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; + if (serverResponse.body && serverResponse.body.error) { errorMessage += `: ${serverResponse.body.error}`; } + logError(errorMessage); + return bids; + } + (serverResponse.body.tags || []).forEach(serverBid => { + try { + const bid = eachBidCallback(serverBid); + if (bid) { + bids.push(bid); + } + } catch (e) { + // Do nothing + } + }); + return bids; +} diff --git a/libraries/liveIntentId/externalIdSystem.js b/libraries/liveIntentId/externalIdSystem.js new file mode 100644 index 00000000000..9fcb9e6da1b --- /dev/null +++ b/libraries/liveIntentId/externalIdSystem.js @@ -0,0 +1,174 @@ +import { logError } from '../../src/utils.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js'; +import { submodule } from '../../src/hook.js'; +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, parseRequestedAttributes, composeIdObject, eids, GVLID, PRIMARY_IDS, makeSourceEventToSend } from './shared.js' + +// Reference to the client for the liQHub. +let cachedClientRef + +/** + * This function is used in tests. + */ +export function resetSubmodule() { + cachedClientRef = undefined +} + +window.liQHub = window.liQHub ?? [] + +function initializeClient(configParams) { + // Only initialize once. + if (cachedClientRef != null) return cachedClientRef + + const clientRef = {} + + const clientDetails = { name: 'prebid', version: '$prebid.version$' } + + const collectConfig = configParams.liCollectConfig ?? {}; + + let integration + if (collectConfig.appId != null) { + integration = { type: 'application', appId: collectConfig.appId, publisherId: configParams.publisherId } + } else if (configParams.distributorId != null && configParams.publisherId == null) { + integration = { type: 'distributor', distributorId: configParams.distributorId } + } else { + integration = { type: 'custom', publisherId: configParams.publisherId, distributorId: configParams.distributorId } + } + + const partnerCookies = new Set(configParams.identifiersToResolve ?? []); + + const collectSettings = { timeout: collectConfig.ajaxTimeout ?? DEFAULT_AJAX_TIMEOUT } + + let identityPartner + if (collectConfig.appId == null && configParams.distributorId != null) { + identityPartner = configParams.distributorId + } else if (configParams.partner != null) { + identityPartner = configParams.partner + } else { + identityPartner = 'prebid' + } + + const resolveSettings = { + identityPartner, + timeout: configParams.ajaxTimeout ?? DEFAULT_AJAX_TIMEOUT + } + + let idCookieSettings + if (configParams.fpid != null) { + const fpidConfig = configParams.fpid + let source + if (fpidConfig.strategy === 'html5') { + source = 'local_storage' + } else { + source = fpidConfig.strategy + } + idCookieSettings = { idCookieSettings: { type: 'provided', source, key: fpidConfig.name } }; + } else { + idCookieSettings = {} + } + + function loadConsent() { + const consent = {} + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString != null) { + consent.usPrivacy = { consentString: usPrivacyString } + } + const gdprConsent = gdprDataHandler.getConsentData() + if (gdprConsent != null) { + consent.gdpr = gdprConsent + } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent != null) { + consent.gpp = { consentString: gppConsent.gppString, applicableSections: gppConsent.applicableSections } + } + + return consent + } + const consent = loadConsent() + + window.liQHub.push({ + type: 'register_client', + clientRef, + clientDetails, + integration, + consent, + partnerCookies, + collectSettings, + ...idCookieSettings, + resolveSettings + }) + + let sourceEvent = makeSourceEventToSend(configParams) + if (sourceEvent != null) { + window.liQHub.push({ type: 'collect', clientRef, sourceEvent }) + } + + cachedClientRef = clientRef + return clientRef +} + +/** + * Create requestedAttributes array to pass to LiveConnect. + * @function + * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } + * @returns {Array} + */ + +function resolve(configParams, clientRef, callback) { + function onFailure(error) { + logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); + callback(); + } + + const onSuccess = [{ type: 'callback', callback }] + + window.liQHub.push({ + type: 'resolve', + clientRef, + requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides), + onFailure, + onSuccess + }) +} + +/** + * @typedef {import('../../modules/userId/index.js').Submodule} Submodule + */ + +/** @type {Submodule} */ +export const liveIntentExternalIdSubmodule = { + /** + * Used to link submodule with config. + * @type {string} + */ + name: MODULE_NAME, + gvlid: GVLID, + + /** + * Decode the stored id value for passing to bid requests. + * @function + */ + decode(value, config) { + const configParams = config?.params ?? {}; + + // Ensure client is initialized and we fired at least one collect request. + initializeClient(configParams) + + return composeIdObject(value); + }, + + /** + * Performs action to obtain id and return a value in the callback's response argument. + * @function + */ + getId(config) { + const configParams = config?.params ?? {}; + + const clientRef = initializeClient(configParams) + + return { callback: function(cb) { resolve(configParams, clientRef, cb); } }; + }, + primaryIds: PRIMARY_IDS, + eids +}; + +submodule('userId', liveIntentExternalIdSubmodule); diff --git a/libraries/liveIntentId/idSystem.js b/libraries/liveIntentId/idSystem.js new file mode 100644 index 00000000000..a9b8052c752 --- /dev/null +++ b/libraries/liveIntentId/idSystem.js @@ -0,0 +1,234 @@ +/** + * This module adds LiveIntentId to the User ID module. + * The {@link module:modules/userId} module is required. + * @module modules/idSystem + * @requires module:modules/userId + */ +import { triggerPixel, logError } from '../../src/utils.js'; +import { ajaxBuilder } from '../../src/ajax.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../src/adapterManager.js'; +import { submodule } from '../../src/hook.js'; +import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports +import { getStorageManager } from '../../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../../src/activities/modules.js'; +import { DEFAULT_AJAX_TIMEOUT, MODULE_NAME, composeIdObject, eids, GVLID, DEFAULT_DELAY, PRIMARY_IDS, parseRequestedAttributes, makeSourceEventToSend } from './shared.js' + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const EVENTS_TOPIC = 'pre_lips'; + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); +const calls = { + ajaxGet: (url, onSuccess, onError, timeout, headers) => { + ajaxBuilder(timeout)( + url, + { + success: onSuccess, + error: onError + }, + undefined, + { + method: 'GET', + withCredentials: true, + customHeaders: headers + } + ) + }, + pixelGet: (url, onload) => triggerPixel(url, onload) +} + +let eventFired = false; +let liveConnect = null; + +/** + * This function is used in tests. + */ +export function reset() { + if (window && window.liQ_instances) { + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); + window.liQ_instances = []; + } + liveIntentIdSubmodule.setModuleMode(null); + eventFired = false; + liveConnect = null; +} + +/** + * This function is used in tests. + */ +export function setEventFiredFlag() { + eventFired = true; +} + +function parseLiveIntentCollectorConfig(collectConfig) { + const config = {}; + 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); + config.ajaxTimeout = collectConfig.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; + return config; +} + +/** + * Create requestedAttributes array to pass to LiveConnect. + * @function + * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } + * @returns {Array} + */ + +function initializeLiveConnect(configParams) { + if (liveConnect) { + return liveConnect; + } + + configParams = configParams || {}; + const fpidConfig = configParams.fpid || {}; + + const publisherId = configParams.publisherId || 'any'; + const identityResolutionConfig = { + publisherId: publisherId, + requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides), + extraAttributes: { + ipv4: configParams.ipv4, + ipv6: configParams.ipv6 + } + }; + if (configParams.url) { + identityResolutionConfig.url = configParams.url; + }; + + identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; + + const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); + + if (!liveConnectConfig.appId && configParams.distributorId) { + liveConnectConfig.distributorId = configParams.distributorId; + identityResolutionConfig.source = configParams.distributorId; + } else { + identityResolutionConfig.source = configParams.partner || 'prebid'; + } + + liveConnectConfig.wrapperName = 'prebid'; + liveConnectConfig.trackerVersion = '$prebid.version$'; + liveConnectConfig.identityResolutionConfig = identityResolutionConfig; + liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; + liveConnectConfig.fireEventDelay = configParams.fireEventDelay; + + liveConnectConfig.idCookie = {}; + liveConnectConfig.idCookie.name = fpidConfig.name; + liveConnectConfig.idCookie.strategy = fpidConfig.strategy == 'html5' ? 'localStorage' : fpidConfig.strategy; + + const usPrivacyString = uspDataHandler.getConsentData(); + if (usPrivacyString) { + liveConnectConfig.usPrivacyString = usPrivacyString; + } + const gdprConsent = gdprDataHandler.getConsentData(); + if (gdprConsent) { + liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; + liveConnectConfig.gdprConsent = gdprConsent.consentString; + } + const gppConsent = gppDataHandler.getConsentData(); + if (gppConsent) { + liveConnectConfig.gppString = gppConsent.gppString; + liveConnectConfig.gppApplicableSections = gppConsent.applicableSections; + } + // The second param is the storage object, LS & Cookie manipulation uses PBJS. + // The third param is the ajax and pixel object, the AJAX and pixel use PBJS. + liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); + + const sourceEvent = makeSourceEventToSend(configParams) + if (sourceEvent != null) { + liveConnect.push(sourceEvent); + } + return liveConnect; +} + +function tryFireEvent() { + if (!eventFired && liveConnect) { + const eventDelay = liveConnect.config.fireEventDelay || DEFAULT_DELAY; + setTimeout(() => { + const instances = window.liQ_instances; + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); + if (!eventFired && liveConnect) { + liveConnect.fire(); + } + }, eventDelay); + } +} + +/** @type {Submodule} */ +export const liveIntentIdSubmodule = { + moduleMode: '$$LIVE_INTENT_MODULE_MODE$$', + /** + * Used to link submodule with config. + * @type {string} + */ + name: MODULE_NAME, + gvlid: GVLID, + setModuleMode(mode) { + this.moduleMode = mode; + }, + getInitializer() { + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); + }, + + /** + * 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 {SubmoduleConfig|undefined} config + * @returns {{lipb:Object}} + */ + decode(value, config) { + const configParams = (config && config.params) || {}; + + if (!liveConnect) { + initializeLiveConnect(configParams); + } + tryFireEvent(); + + return composeIdObject(value); + }, + + /** + * 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) || {}; + const liveConnect = initializeLiveConnect(configParams); + if (!liveConnect) { + return; + } + tryFireEvent(); + const result = function(callback) { + liveConnect.resolve( + response => { + callback(response); + }, + error => { + logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); + callback(); + } + ) + } + + return { callback: result }; + }, + primaryIds: PRIMARY_IDS, + eids +}; + +submodule('userId', liveIntentIdSubmodule); diff --git a/libraries/liveIntentId/shared.js b/libraries/liveIntentId/shared.js new file mode 100644 index 00000000000..509f91e44d9 --- /dev/null +++ b/libraries/liveIntentId/shared.js @@ -0,0 +1,276 @@ +import {UID1_EIDS} from '../uid1Eids/uid1Eids.js'; +import {UID2_EIDS} from '../uid2Eids/uid2Eids.js'; +import { getRefererInfo } from '../../src/refererDetection.js'; +import { coppaDataHandler } from '../../src/adapterManager.js'; + +export const PRIMARY_IDS = ['libp']; +export const GVLID = 148; +export const DEFAULT_AJAX_TIMEOUT = 5000; +export const DEFAULT_DELAY = 500; +export const MODULE_NAME = 'liveIntentId'; +export const LI_PROVIDER_DOMAIN = 'liveintent.com'; +export const DEFAULT_REQUESTED_ATTRIBUTES = { 'nonId': true }; + +export function parseRequestedAttributes(overrides) { + function renameAttribute(attribute) { + if (attribute === 'fpid') { + return 'idCookie'; + } else { + return attribute; + }; + } + function createParameterArray(config) { + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); + } + if (typeof overrides === 'object') { + return createParameterArray({...DEFAULT_REQUESTED_ATTRIBUTES, ...overrides}); + } else { + return createParameterArray(DEFAULT_REQUESTED_ATTRIBUTES); + } +} + +export function makeSourceEventToSend(configParams) { + const sourceEvent = {} + let nonEmpty = false + if (typeof configParams.emailHash === 'string') { + nonEmpty = true + sourceEvent.emailHash = configParams.emailHash + } + if (typeof configParams.ipv4 === 'string') { + nonEmpty = true + sourceEvent.ipv4 = configParams.ipv4 + } + if (typeof configParams.ipv6 === 'string') { + nonEmpty = true + sourceEvent.ipv6 = configParams.ipv6 + } + if (typeof configParams.userAgent === 'string') { + nonEmpty = true + sourceEvent.userAgent = configParams.userAgent + } + + if (nonEmpty) { + return sourceEvent + } +} + +export function composeIdObject(value) { + const result = {}; + + // old versions stored lipbid in unifiedId. Ensure that we can still read the data. + const lipbid = value.nonId || value.unifiedId + if (lipbid) { + const lipb = { ...value, lipbid }; + delete lipb.unifiedId; + result.lipb = lipb; + } + + // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. + // As adapters are applied in lexicographical order, we will always + // be overwritten by the 'proper' uid2 module if it is present. + if (value.uid2) { + result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.bidswitch) { + result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.medianet) { + result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.magnite) { + result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.index) { + result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.openx) { + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.pubmatic) { + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.sovrn) { + result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.idCookie) { + if (!coppaDataHandler.getCoppa()) { + result.lipb = { ...result.lipb, fpid: value.idCookie }; + result.fpid = { 'id': value.idCookie }; + } + delete result.lipb.idCookie; + } + + if (value.thetradedesk) { + result.lipb = {...result.lipb, tdid: value.thetradedesk} + result.tdid = { 'id': value.thetradedesk, ext: { rtiPartner: 'TDID', provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } + delete result.lipb.thetradedesk + } + + if (value.sharethrough) { + result.sharethrough = { 'id': value.sharethrough, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.sonobi) { + result.sonobi = { 'id': value.sonobi, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + if (value.vidazoo) { + result.vidazoo = { 'id': value.vidazoo, ext: { provider: LI_PROVIDER_DOMAIN } } + } + + return result +} + +export const eids = { + ...UID1_EIDS, + ...UID2_EIDS, + 'lipb': { + getValue: function(data) { + return data.lipbid; + }, + source: 'liveintent.com', + atype: 3, + getEidExt: function(data) { + if (Array.isArray(data.segments) && data.segments.length) { + return { + segments: data.segments + }; + } + } + }, + 'bidswitch': { + source: 'bidswitch.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'medianet': { + source: 'media.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'magnite': { + source: 'rubiconproject.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'index': { + source: 'liveintent.indexexchange.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'openx': { + source: 'openx.net', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'pubmatic': { + source: 'pubmatic.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'sovrn': { + source: 'liveintent.sovrn.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'fpid': { + source: 'fpid.liveintent.com', + atype: 1, + getValue: function(data) { + return data.id; + } + }, + 'sharethrough': { + source: 'sharethrough.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'sonobi': { + source: 'liveintent.sonobi.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + }, + 'vidazoo': { + source: 'liveintent.vidazoo.com', + atype: 3, + getValue: function(data) { + return data.id; + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext; + } + } + } +} diff --git a/libraries/mediaImpactUtils/index.js b/libraries/mediaImpactUtils/index.js new file mode 100644 index 00000000000..a56761d8ed4 --- /dev/null +++ b/libraries/mediaImpactUtils/index.js @@ -0,0 +1,61 @@ +import { buildUrl } from '../../src/utils.js'; +import { ajax } from '../../src/ajax.js'; + +/** + * Builds the bid requests and beacon parameters. + * @param {Array} validBidRequests - The array of valid bid requests. + * @param {string} referer - The referer URL. + * @returns {Object} - An object containing bidRequests and beaconParams. + */ +export function buildBidRequestsAndParams(validBidRequests, referer) { + const bidRequests = []; + const beaconParams = { tag: [], partner: [], sizes: [], referer: encodeURIComponent(referer) }; + + validBidRequests.forEach(function (validBidRequest) { + const sizes = validBidRequest.params.sizes || validBidRequest.sizes; + + const bidRequestObject = { + adUnitCode: validBidRequest.adUnitCode, + sizes: sizes, + bidId: validBidRequest.bidId, + referer: referer, + }; + + if (parseInt(validBidRequest.params.unitId)) { + bidRequestObject.unitId = parseInt(validBidRequest.params.unitId); + beaconParams.tag.push(validBidRequest.params.unitId); + } + + if (parseInt(validBidRequest.params.partnerId)) { + bidRequestObject.unitId = 0; + bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId); + beaconParams.partner.push(validBidRequest.params.partnerId); + } + + bidRequests.push(bidRequestObject); + beaconParams.sizes.push(joinSizesToString(sizes)); + }); + + // Finalize beaconParams + if (beaconParams.partner.length > 0) { + beaconParams.partner = beaconParams.partner.join(','); + } else { + delete beaconParams.partner; + } + beaconParams.tag = beaconParams.tag.join(','); + beaconParams.sizes = beaconParams.sizes.join(','); + + return { bidRequests, beaconParams }; +} + +export function joinSizesToString(sizes) { + return sizes.map(size => size.join('x')).join('|'); +} + +export function postRequest(endpoint, data) { + ajax(endpoint, null, data, { method: 'POST' }); +} + +export function buildEndpointUrl(protocol, hostname, pathname, searchParams) { + return buildUrl({ protocol, hostname, pathname, search: searchParams }); +} diff --git a/libraries/mgidUtils/mgidUtils.js b/libraries/mgidUtils/mgidUtils.js new file mode 100644 index 00000000000..9ac84e231b7 --- /dev/null +++ b/libraries/mgidUtils/mgidUtils.js @@ -0,0 +1,73 @@ +import { + isPlainObject, + isArray, + isStr, + isNumber, +} from '../../src/utils.js'; +import { config } from '../../src/config.js'; +import { USERSYNC_DEFAULT_CONFIG } from '../../src/userSync.js'; + +const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; +const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const spb = isPlainObject(config.getConfig('userSync')) && + isNumber(config.getConfig('userSync').syncsPerBidder) + ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; + + if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixels = []; + if (serverResponses && + isArray(serverResponses) && + serverResponses.length > 0 && + isPlainObject(serverResponses[0].body) && + isPlainObject(serverResponses[0].body.ext) && + isArray(serverResponses[0].body.ext.cm) && + serverResponses[0].body.ext.cm.length > 0) { + pixels = serverResponses[0].body.ext.cm; + } + + const syncs = []; + const query = []; + query.push('cbuster={cbuster}'); + query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); + if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { + query.push('gdpr=1'); + } else { + query.push('gdpr=0'); + } + if (isPlainObject(uspConsent) && uspConsent?.consentString) { + query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); + } + if (isPlainObject(gppConsent) && gppConsent?.gppString) { + query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); + } + if (config.getConfig('coppa')) { + query.push('coppa=1') + } + const q = query.join('&') + if (syncOptions.iframeEnabled) { + syncs.push({ + type: 'iframe', + url: IFRAME_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) + }); + } else if (syncOptions.pixelEnabled) { + if (pixels.length === 0) { + for (let i = 0; i < spb; i++) { + syncs.push({ + type: 'image', + url: PIXEL_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required + }); + } + } else { + for (let i = 0; i < spb && i < pixels.length; i++) { + syncs.push({ + type: 'image', + url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) + }); + } + } + } + return syncs; + } +} diff --git a/libraries/mspa/activityControls.js b/libraries/mspa/activityControls.js index eaf515e2385..eb68259d585 100644 --- a/libraries/mspa/activityControls.js +++ b/libraries/mspa/activityControls.js @@ -97,6 +97,9 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat if (consent == null) { return {allow: false, reason: 'consent data not available'}; } + if (consent.Version !== 1) { + return {allow: false, reason: `unsupported consent specification version "${consent.Version}"`} + } if (denies(consent)) { return {allow: false}; } @@ -105,7 +108,7 @@ export function mspaRule(sids, getConsent, denies, applicableSids = () => gppDat } function flatSection(subsections) { - if (subsections == null) return subsections; + if (!Array.isArray(subsections)) return subsections; return subsections.reduceRight((subsection, consent) => { return Object.assign(consent, subsection); }, {}); diff --git a/libraries/navigatorData/navigatorData.js b/libraries/navigatorData/navigatorData.js new file mode 100644 index 00000000000..f1a34fc51eb --- /dev/null +++ b/libraries/navigatorData/navigatorData.js @@ -0,0 +1,29 @@ +export function getHLen(win = window) { + let hLen; + try { + hLen = win.top.history.length; + } catch (error) { + hLen = undefined; + } + return hLen; +} + +export function getHC(win = window) { + let hc; + try { + hc = win.top.navigator.hardwareConcurrency; + } catch (error) { + hc = undefined; + } + return hc; +} + +export function getDM(win = window) { + let dm; + try { + dm = win.top.navigator.deviceMemory; + } catch (error) { + dm = undefined; + } + return dm; +} diff --git a/libraries/ortb2.5Translator/translator.js b/libraries/ortb2.5Translator/translator.js index 1afad516ef0..6dd6d247d1c 100644 --- a/libraries/ortb2.5Translator/translator.js +++ b/libraries/ortb2.5Translator/translator.js @@ -1,10 +1,12 @@ import {deepAccess, deepSetValue, logError} from '../../src/utils.js'; export const EXT_PROMOTIONS = [ + 'device.sua', 'source.schain', 'regs.gdpr', 'regs.us_privacy', 'regs.gpp', + 'regs.gpp_sid', 'user.consent', 'user.eids' ]; diff --git a/libraries/ortbConverter/converter.js b/libraries/ortbConverter/converter.js index c367aec268a..9cef8898c39 100644 --- a/libraries/ortbConverter/converter.js +++ b/libraries/ortbConverter/converter.js @@ -117,7 +117,7 @@ export function ortbConverter({ throw new Error('ortbRequest passed to `fromORTB` must be the same object returned by `toORTB`') } function augmentContext(ctx, extraParams = {}) { - return Object.assign(ctx, {ortbRequest: request}, extraParams, ctx); + return Object.assign(ctx, {ortbRequest: request}, extraParams); } const impsById = Object.fromEntries((request.imp || []).map(imp => [imp.id, imp])); const bidResponses = (response.seatbid || []).flatMap(seatbid => diff --git a/libraries/ortbConverter/lib/composer.js b/libraries/ortbConverter/lib/composer.js index 0ceff7f9edb..477d4e10890 100644 --- a/libraries/ortbConverter/lib/composer.js +++ b/libraries/ortbConverter/lib/composer.js @@ -11,13 +11,13 @@ const SORTED = new WeakMap(); /** * - * @param {Object[string, Component]} components to compose - * @param {Object[string, function|boolean]} overrides a map from component name, to a function that should override that component. + * @param {Object.} components - An object where keys are component names and values are components to compose. + * @param {Object.} overrides - A map from component names to functions that should override those components. * Override functions are replacements, except that they get the original function they are overriding as their first argument. If the override * is `false`, the component is disabled. * - * @return a function that will run all components in order of priority, with functions from `overrides` taking - * precedence over components that match names + * @return {function} - A function that will run all components in order of priority, with functions from `overrides` taking + * precedence over components that match names. */ export function compose(components, overrides = {}) { if (!SORTED.has(components)) { diff --git a/libraries/ortbConverter/lib/sizes.js b/libraries/ortbConverter/lib/sizes.js deleted file mode 100644 index 16b75048203..00000000000 --- a/libraries/ortbConverter/lib/sizes.js +++ /dev/null @@ -1,14 +0,0 @@ -import {parseSizesInput} from '../../../src/utils.js'; - -export function sizesToFormat(sizes) { - sizes = parseSizesInput(sizes); - - // get sizes in form [{ w: , h: }, ...] - return sizes.map(size => { - const [width, height] = size.split('x'); - return { - w: parseInt(width, 10), - h: parseInt(height, 10) - }; - }); -} diff --git a/libraries/ortbConverter/processors/banner.js b/libraries/ortbConverter/processors/banner.js index 2d0136c84b2..fca9598022b 100644 --- a/libraries/ortbConverter/processors/banner.js +++ b/libraries/ortbConverter/processors/banner.js @@ -1,6 +1,13 @@ -import {createTrackPixelHtml, deepAccess, encodeMacroURI, inIframe, mergeDeep} from '../../../src/utils.js'; +import { + createTrackPixelHtml, + deepAccess, + inIframe, + mergeDeep, + sizesToSizeTuples, + sizeTupleToRtbSize, + encodeMacroURI +} from '../../../src/utils.js'; import {BANNER} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; /** * fill in a request `imp` with banner parameters from `bidRequest`. @@ -14,7 +21,7 @@ export function fillBannerImp(imp, bidRequest, context) { topframe: inIframe() === true ? 0 : 1 }; if (bannerParams.sizes) { - banner.format = sizesToFormat(bannerParams.sizes); + banner.format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); } if (bannerParams.hasOwnProperty('pos')) { banner.pos = bannerParams.pos; diff --git a/libraries/ortbConverter/processors/default.js b/libraries/ortbConverter/processors/default.js index d92a51daba2..f9c7e3baefc 100644 --- a/libraries/ortbConverter/processors/default.js +++ b/libraries/ortbConverter/processors/default.js @@ -61,6 +61,12 @@ export const DEFAULT_PROCESSORS = { delete imp.ext?.data?.pbadslot; } } + }, + secure: { + // should set imp.secure to 1 unless publisher has set it + fn(imp, bidRequest) { + imp.secure = imp.secure ?? 1; + } } }, [BID_RESPONSE]: { @@ -100,6 +106,13 @@ export const DEFAULT_PROCESSORS = { if (bid.ext?.dsa) { bidResponse.meta.dsa = bid.ext.dsa; } + if (bid.cat) { + bidResponse.meta.primaryCatId = bid.cat[0]; + bidResponse.meta.secondaryCatIds = bid.cat.slice(1); + } + if (bid.attr) { + bidResponse.meta.attr = bid.attr; + } } } } diff --git a/libraries/ortbConverter/processors/video.js b/libraries/ortbConverter/processors/video.js index c38231d9002..85ab1cd6ecf 100644 --- a/libraries/ortbConverter/processors/video.js +++ b/libraries/ortbConverter/processors/video.js @@ -1,35 +1,7 @@ -import {deepAccess, isEmpty, logWarn, mergeDeep} from '../../../src/utils.js'; +import {deepAccess, isEmpty, logWarn, mergeDeep, sizesToSizeTuples, sizeTupleToRtbSize} from '../../../src/utils.js'; import {VIDEO} from '../../../src/mediaTypes.js'; -import {sizesToFormat} from '../lib/sizes.js'; -// parameters that share the same name & semantics between pbjs adUnits and imp.video -const ORTB_VIDEO_PARAMS = new Set([ - 'pos', - 'placement', - 'plcmt', - 'api', - 'mimes', - 'protocols', - 'playbackmethod', - 'minduration', - 'maxduration', - 'w', - 'h', - 'startdelay', - 'placement', - 'linearity', - 'skip', - 'skipmin', - 'skipafter', - 'minbitrate', - 'maxbitrate', - 'delivery', - 'playbackend' -]); - -const PLACEMENT = { - 'instream': 1, -} +import {ORTB_VIDEO_PARAMS} from '../../../src/video.js'; export function fillVideoImp(imp, bidRequest, context) { if (context.mediaType && context.mediaType !== VIDEO) return; @@ -37,20 +9,18 @@ export function fillVideoImp(imp, bidRequest, context) { const videoParams = deepAccess(bidRequest, 'mediaTypes.video'); if (!isEmpty(videoParams)) { const video = Object.fromEntries( + // Parameters that share the same name & semantics between pbjs adUnits and imp.video Object.entries(videoParams) .filter(([name]) => ORTB_VIDEO_PARAMS.has(name)) ); if (videoParams.playerSize) { - const format = sizesToFormat(videoParams.playerSize); + const format = sizesToSizeTuples(videoParams.playerSize).map(sizeTupleToRtbSize); if (format.length > 1) { logWarn('video request specifies more than one playerSize; all but the first will be ignored') } Object.assign(video, format[0]); } - const placement = PLACEMENT[videoParams.context]; - if (placement != null) { - video.placement = placement; - } + imp.video = mergeDeep(video, imp.video); } } diff --git a/libraries/paapiTools/buyerOrigins.js b/libraries/paapiTools/buyerOrigins.js new file mode 100644 index 00000000000..ace9b7da073 --- /dev/null +++ b/libraries/paapiTools/buyerOrigins.js @@ -0,0 +1,35 @@ +/* + This list is several known buyer origins for PAAPI auctions. + Bidders should add anyone they like to it. + It is not intended to be comphensive nor maintained by the Core team. + Rather, Bid adapters should simply append additional constants whenever + the need arises in their adapter. + + The goal is to reduce expression of common constants over many + bid adapters attempting to define interestGroupBuyers + in advance of network traffic. + + Bidders should consider updating their interstGroupBuyer list + with server communication for auctions initiated after the first bid response. + + Known buyers without current importers are commented out. If you need one, uncomment it. +*/ + +export const BO_CSR_ONET = 'https://csr.onet.pl'; +// export const BO_DOUBLECLICK_GOOGLEADS = 'https://googleads.g.doubleclick.net'; +// export const BO_DOUBLECLICK_TD = 'https://td.doubleclick.net'; +// export const BO_RTBHOUSE = 'https://f.creativecdn.com'; +// export const BO_CRITEO_US = 'https://fledge.us.criteo.com'; +// export const BO_CRITEO_EU = 'https://fledge.eu.criteo.com'; +// export const BO_CRITEO_AS = 'https://fledge.as.criteo.com'; +// export const BO_CRITEO_GRID_MERCURY = 'https://grid-mercury.criteo.com'; +// export const BO_CRITEO_BIDSWITCH_TRADR = 'https://tradr.bsw-sb.criteo.com'; +// export const BO_CRITEO_BIDSWITCH_SANDBOX = 'https://dsp-paapi-sandbox.bsw-ig.criteo.com'; +// export const BO_APPSPOT = 'https://fledge-buyer-testing-1.uc.r.appspot.com'; +// export const BO_OPTABLE = 'https://ads.optable.co'; +// export const BO_ADROLL = 'https://x.adroll.com'; +// export const BO_ADFORM = 'https://a2.adform.net'; +// export const BO_RETARGETLY = 'https://cookieless-campaign.prd-00.retargetly.com'; +// export const BO_AUDIGENT = 'https://proton.ad.gt'; +// export const BO_YAHOO = 'https://pa.ybp.yahoo.com'; +// export const BO_DOTOMI = 'https://usadmm.dotomi.com'; diff --git a/libraries/pbsExtensions/processors/aliases.js b/libraries/pbsExtensions/processors/aliases.js index 3dcd2c4fd9b..42dea969e6b 100644 --- a/libraries/pbsExtensions/processors/aliases.js +++ b/libraries/pbsExtensions/processors/aliases.js @@ -1,4 +1,5 @@ import adapterManager from '../../../src/adapterManager.js'; +import {config} from '../../../src/config.js'; import {deepSetValue} from '../../../src/utils.js'; export function setRequestExtPrebidAliases(ortbRequest, bidderRequest, context, {am = adapterManager} = {}) { @@ -7,11 +8,22 @@ export function setRequestExtPrebidAliases(ortbRequest, bidderRequest, context, // adding alias only if alias source bidder exists and alias isn't configured to be standalone // pbs adapter if (!bidder || !bidder.getSpec().skipPbsAliasing) { + // set alias deepSetValue( ortbRequest, `ext.prebid.aliases.${bidderRequest.bidderCode}`, am.aliasRegistry[bidderRequest.bidderCode] ); + + // set alias gvlids if present also + const gvlId = config.getConfig(`gvlMapping.${bidderRequest.bidderCode}`) || bidder?.getSpec?.().gvlid; + if (gvlId) { + deepSetValue( + ortbRequest, + `ext.prebid.aliasgvlids.${bidderRequest.bidderCode}`, + gvlId + ); + } } } } diff --git a/libraries/pbsExtensions/processors/params.js b/libraries/pbsExtensions/processors/params.js index 010ffa5b372..dbfbb928953 100644 --- a/libraries/pbsExtensions/processors/params.js +++ b/libraries/pbsExtensions/processors/params.js @@ -1,17 +1,7 @@ -import {auctionManager} from '../../../src/auctionManager.js'; -import adapterManager from '../../../src/adapterManager.js'; import {deepSetValue} from '../../../src/utils.js'; -export function setImpBidParams( - imp, bidRequest, context, - {adUnit, bidderRequests, index = auctionManager.index, bidderRegistry = adapterManager.bidderRegistry} = {}) { +export function setImpBidParams(imp, bidRequest) { let params = bidRequest.params; - const adapter = bidderRegistry[bidRequest.bidder]; - if (adapter && adapter.getSpec().transformBidParams) { - adUnit = adUnit || index.getAdUnit(bidRequest); - bidderRequests = bidderRequests || [context.bidderRequest]; - params = adapter.getSpec().transformBidParams(params, true, adUnit, bidderRequests); - } if (params) { deepSetValue( imp, diff --git a/libraries/precisoUtils/bidNativeUtils.js b/libraries/precisoUtils/bidNativeUtils.js new file mode 100644 index 00000000000..29b39f6d77d --- /dev/null +++ b/libraries/precisoUtils/bidNativeUtils.js @@ -0,0 +1,104 @@ +import { deepAccess, logInfo } from '../../src/utils.js'; +import { NATIVE } from '../../src/mediaTypes.js'; +import { macroReplace } from './bidUtils.js'; + +const TTL = 55; +// Codes defined by OpenRTB Native Ads 1.1 specification +export const OPENRTB = { + NATIVE: { + 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, + }, + } +}; + +/** + * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 + * @returns {object} Prebid native bidObject + */ +export function interpretNativeBid(serverBid) { + return { + requestId: serverBid.impid, + mediaType: NATIVE, + cpm: serverBid.price, + creativeId: serverBid.adid || serverBid.crid, + width: 1, + height: 1, + ttl: TTL, + meta: { + advertiserDomains: serverBid.adomain + }, + netRevenue: true, + currency: 'USD', + // native: interpretNativeAd(serverBid.adm) + native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price)) + } +} + +/** + * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1 + * @returns {object} Prebid bidObject.native + */ + +export function interpretNativeAd(adm) { + try { + const native = JSON.parse(adm).native; + if (native) { + const result = { + clickUrl: encodeURI(native.link.url), + impressionTrackers: native.imptrackers || native.eventtrackers[0].url, + }; + if (native.link.clicktrackers) { + result.clickTrackers = native.link.clicktrackers[0]; + } + + native.assets.forEach(asset => { + switch (asset.id) { + case OPENRTB.NATIVE.ASSET_ID.TITLE: + result.title = deepAccess(asset, 'title.text'); + break; + case OPENRTB.NATIVE.ASSET_ID.IMAGE: + result.image = { + url: encodeURI(asset.img.url), + width: deepAccess(asset, 'img.w'), + height: deepAccess(asset, 'img.h') + }; + break; + case OPENRTB.NATIVE.ASSET_ID.ICON: + result.icon = { + url: encodeURI(asset.img.url), + width: deepAccess(asset, 'img.w'), + height: deepAccess(asset, 'img.h') + }; + break; + case OPENRTB.NATIVE.ASSET_ID.BODY: + result.body = deepAccess(asset, 'data.value'); + break; + case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + result.sponsoredBy = deepAccess(asset, 'data.value'); + break; + case OPENRTB.NATIVE.ASSET_ID.CTA: + result.cta = deepAccess(asset, 'data.value'); + break; + } + }); + return result; + } + } catch (error) { + logInfo('Error in bidUtils interpretNativeAd' + error); + } +} diff --git a/libraries/precisoUtils/bidUtils.js b/libraries/precisoUtils/bidUtils.js new file mode 100644 index 00000000000..8359963cd03 --- /dev/null +++ b/libraries/precisoUtils/bidUtils.js @@ -0,0 +1,249 @@ +import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; +import { replaceAuctionPrice, deepAccess } from '../../src/utils.js'; +import { ajax } from '../../src/ajax.js'; +// import { NATIVE } from '../../src/mediaTypes.js'; +import { consentCheck, getBidFloor } from './bidUtilsCommon.js'; +import { interpretNativeBid } from './bidNativeUtils.js'; + +export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + let req = { + id: validBidRequests[0].auctionId, + imp: validBidRequests.map(slot => mapImpression(slot, bidderRequest)), + user: { + id: validBidRequests[0].userId.pubcid || '', + buyeruid: validBidRequests[0].buyerUid || '', + geo: { + country: validBidRequests[0].params.region || city, + region: validBidRequests[0].params.region || city, + }, + + }, + device: validBidRequests[0].ortb2.device, + site: validBidRequests[0].ortb2.site, + source: validBidRequests[0].ortb2.source, + bcat: validBidRequests[0].ortb2.bcat || validBidRequests[0].params.bcat, + badv: validBidRequests[0].ortb2.badv || validBidRequests[0].params.badv, + wlang: validBidRequests[0].ortb2.wlang || validBidRequests[0].params.wlang, + }; + if (req.device && req.device != 'undefined') { + req.device.geo = { + country: req.user.geo.country, + region: req.user.geo.region, + + }; + }; + req.site.publisher = { + publisherId: validBidRequests[0].params.publisherId + }; + + consentCheck(bidderRequest, req); + return { + method: 'POST', + url: endpoint, + data: req, + + }; +} + +export function interpretResponse(serverResponse) { + const bidsValue = [] + const bidResponse = serverResponse.body + bidResponse.seatbid.forEach(seat => { + seat.bid.forEach(bid => { + bidsValue.push({ + requestId: bid.impid, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid, + ad: macroReplace(bid.adm, bid.price), + currency: 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: bid.adomain || '', + }, + }) + }) + }) + return bidsValue +} + +export function onBidWon(bid) { + if (bid.nurl) { + const resolvedNurl = replaceAuctionPrice(bid.nurl, bid.price); + ajax(resolvedNurl); + } +} + +export function macroReplace(adm, cpm) { + let replacedadm = replaceAuctionPrice(adm, cpm); + return replacedadm; +} + +function mapImpression(slot, bidderRequest) { + const imp = { + id: slot.bidId, + bidFloor: getBidFloor(slot), + }; + + if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) { + imp.native = mapNative(slot) + } else { + imp.banner = mapBanner(slot) + } + return imp +} + +function mapNative(slot) { + if (slot.mediaType === 'native' || deepAccess(slot, 'mediaTypes.native')) { + let request = { + assets: slot.nativeOrtbRequest.assets || slot.nativeParams.ortb.assets, + ver: '1.2' + }; + return { + request: JSON.stringify(request) + } + } +} + +function mapBanner(slot) { + if (slot.mediaTypes.banner) { + let format = (slot.mediaTypes.banner.sizes || slot.sizes).map(size => { + return { w: size[0], h: size[1] } + }); + + return { + format + } + } +} + +export function buildBidResponse(serverResponse) { + const responseBody = serverResponse.body; + const bids = []; + responseBody.seatbid.forEach(seat => { + seat.bid.forEach(serverBid => { + if (!serverBid.price) { + return; + } + if (serverBid.adm.indexOf('{') === 0) { + let interpretedBid = interpretNativeBid(serverBid); + bids.push(interpretedBid + ); + } else { + bids.push({ + requestId: serverBid.impid, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.crid, + ad: macroReplace(serverBid.adm, serverBid.price), + currency: 'USD', + netRevenue: true, + ttl: 300, + meta: { + advertiserDomains: serverBid.adomain || '', + }, + }); + } + }) + }); + return bids; +} + +// export function interpretNativeAd(adm) { +// try { +// // logInfo('adm::' + adm); +// const native = JSON.parse(adm).native; +// if (native) { +// const result = { +// clickUrl: encodeURI(native.link.url), +// impressionTrackers: native.eventtrackers[0].url, +// }; +// if (native.link.clicktrackers[0]) { +// result.clickTrackers = native.link.clicktrackers[0]; +// } + +// native.assets.forEach(asset => { +// switch (asset.id) { +// case OPENRTB.NATIVE.ASSET_ID.TITLE: +// result.title = deepAccess(asset, 'title.text'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.IMAGE: +// result.image = { +// url: encodeURI(asset.img.url), +// width: deepAccess(asset, 'img.w'), +// height: deepAccess(asset, 'img.h') +// }; +// break; +// case OPENRTB.NATIVE.ASSET_ID.ICON: +// result.icon = { +// url: encodeURI(asset.img.url), +// width: deepAccess(asset, 'img.w'), +// height: deepAccess(asset, 'img.h') +// }; +// break; +// case OPENRTB.NATIVE.ASSET_ID.DATA: +// result.body = deepAccess(asset, 'data.value'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.SPONSORED: +// result.sponsoredBy = deepAccess(asset, 'data.value'); +// break; +// case OPENRTB.NATIVE.ASSET_ID.CTA: +// result.cta = deepAccess(asset, 'data.value'); +// break; +// } +// }); +// return result; +// } +// } catch (error) { +// logInfo('Error in bidUtils interpretNativeAd' + error); +// } +// } + +// export const OPENRTB = { +// NATIVE: { +// 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, +// }, +// } +// }; + +// /** +// * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 +// * @returns {object} Prebid native bidObject +// */ +// export function interpretNativeBid(serverBid) { +// return { +// requestId: serverBid.impid, +// mediaType: NATIVE, +// cpm: serverBid.price, +// creativeId: serverBid.adid || serverBid.crid, +// width: 1, +// height: 1, +// ttl: 56, +// meta: { +// advertiserDomains: serverBid.adomain +// }, +// netRevenue: true, +// currency: 'USD', +// native: interpretNativeAd(macroReplace(serverBid.adm, serverBid.price)) +// } +// } diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js new file mode 100644 index 00000000000..a8ea97efcaf --- /dev/null +++ b/libraries/precisoUtils/bidUtilsCommon.js @@ -0,0 +1,162 @@ +import { config } from '../../src/config.js'; +import { + isFn, + isStr, + deepAccess, + getWindowTop, + triggerPixel +} from '../../src/utils.js'; +import { BANNER, VIDEO, NATIVE } from '../../src/mediaTypes.js'; + +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; + } +} + +export function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + return bidFloor?.floor; + } catch (_) { + return 0 + } +} + +export function isBidRequestValid(bid) { + return Boolean(bid.bidId && bid.params && bid.params.placementId); +} + +export const buildBidRequests = (adurl) => (validBidRequests = [], bidderRequest) => { + const winTop = 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] : '', + host: location.host, + page: location.pathname, + placements: placements + }; + consentCheck(bidderRequest, request); + 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) + }; + + if (typeof bid.userId !== 'undefined') { + placement.userId = bid.userId; + } + + const mediaType = bid.mediaTypes; + + if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { + placement.sizes = mediaType[BANNER].sizes; + placement.adFormat = BANNER; + } + + placements.push(placement); + } + + return { + method: 'POST', + url: adurl, + data: request + }; +} + +export function 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; +} + +export function consentCheck(bidderRequest, req) { + if (bidderRequest) { + if (bidderRequest.uspConsent) { + req.ccpa = bidderRequest.uspConsent; + } + if (bidderRequest.gdprConsent) { + req.gdpr = bidderRequest.gdprConsent + } + if (bidderRequest.gppConsent) { + req.gpp = bidderRequest.gppConsent; + } + } +} + +export const buildUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint) => { + let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + const isCk2trk = syncEndpoint.includes('ck.2trk.info'); + + let syncUrl = isCk2trk ? syncEndpoint : `${syncEndpoint}/${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}`; + } + } else { + syncUrl += isCk2trk ? `&gdpr=0&gdpr_consent=` : ''; + } + + if (isCk2trk) { + syncUrl += uspConsent ? `&us_privacy=${uspConsent}` : `&us_privacy=`; + syncUrl += (syncOptions.iframeEnabled) ? `&t=4` : `&t=2` + } else { + if (uspConsent && uspConsent.consentString) { + syncUrl += `&ccpa_consent=${uspConsent.consentString}`; + } + const coppa = config.getConfig('coppa') ? 1 : 0; + syncUrl += `&coppa=${coppa}`; + } + + return [{ + type: syncType, + url: syncUrl + }]; +} + +export function bidWinReport (bid) { + const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); + triggerPixel(bid.nurl); + } +} diff --git a/libraries/processResponse/index.js b/libraries/processResponse/index.js new file mode 100644 index 00000000000..5ca2e9329f3 --- /dev/null +++ b/libraries/processResponse/index.js @@ -0,0 +1,12 @@ +import { logError } from '../../src/utils.js'; + +export function getBidFromResponse(respItem, LOG_ERROR_MESS) { + if (!respItem) { + logError(LOG_ERROR_MESS.emptySeatbid); + } else if (!respItem.bid) { + logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); + } else if (!respItem.bid[0]) { + logError(LOG_ERROR_MESS.noBid); + } + return respItem && respItem.bid && respItem.bid[0]; +} diff --git a/libraries/riseUtils/index.js b/libraries/riseUtils/index.js new file mode 100644 index 00000000000..2bc337b9c55 --- /dev/null +++ b/libraries/riseUtils/index.js @@ -0,0 +1,332 @@ +import { + isArray, + isFn, + deepAccess, + isEmpty, + contains, + isInteger, + getBidIdParameter, + isPlainObject +} from '../../src/utils.js'; +import { BANNER, VIDEO } from '../../src/mediaTypes.js'; +import {config} from '../../src/config.js'; + +export function getFloor(bid, mediaType) { + if (!isFn(bid.getFloor)) { + return 0; + } + let floorResult = bid.getFloor({ + currency: 'USD', + mediaType: mediaType, + size: '*' + }); + return isPlainObject(floorResult) && floorResult.currency === 'USD' && floorResult.floor ? floorResult.floor : 0; +} + +export function getSizesArray(bid, mediaType) { + let sizesArray = []; + + if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { + sizesArray = bid.mediaTypes[mediaType].sizes; + } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { + sizesArray = bid.sizes; + } + + return sizesArray; +} + +export function getSupplyChain(schainObject) { + if (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; +} + +export function getEncodedValIfNotEmpty(val) { + return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; +} + +export function getAllowedSyncMethod(filterSettings, bidderCode) { + const iframeConfigsToCheck = ['all', 'iframe']; + const pixelConfigToCheck = 'image'; + if (filterSettings && iframeConfigsToCheck.some(config => isSyncMethodAllowed(filterSettings[config], bidderCode))) { + return 'iframe'; + } + if (!filterSettings || !filterSettings[pixelConfigToCheck] || isSyncMethodAllowed(filterSettings[pixelConfigToCheck], bidderCode)) { + return 'pixel'; + } +} + +export function isSyncMethodAllowed(syncRule, bidderCode) { + if (!syncRule) { + return false; + } + const isInclude = syncRule.filter === 'include'; + const bidders = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; + return isInclude && contains(bidders, bidderCode); +} + +export function getEndpoint(testMode, baseUrl, modes) { + const protocol = baseUrl.startsWith('http') ? '' : 'https://'; + const url = baseUrl.endsWith('/') ? baseUrl : `${baseUrl}/`; + return testMode + ? `${protocol}${url}${modes.TEST}` + : `${protocol}${url}${modes.PRODUCTION}`; +} + +export 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'; +} + +export function generateBidsParams(validBidRequests, bidderRequest) { + const bidsArray = []; + + if (validBidRequests.length) { + validBidRequests.forEach(bid => { + bidsArray.push(generateBidParameters(bid, bidderRequest)); + }); + } + + return bidsArray; +} + +export function generateBidParameters(bid, bidderRequest) { + const { params } = bid; + const mediaType = isBanner(bid) ? BANNER : VIDEO; + const sizesArray = getSizesArray(bid, mediaType); + + if (isNaN(params.floorPrice)) { + params.floorPrice = 0; + } + + const bidObject = { + mediaType, + adUnitCode: getBidIdParameter('adUnitCode', bid), + sizes: sizesArray, + floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), + bidId: getBidIdParameter('bidId', bid), + loop: bid.bidderRequestsCount || 0, + bidderRequestId: getBidIdParameter('bidderRequestId', bid), + transactionId: bid.ortb2Imp?.ext?.tid || '', + coppa: 0, + }; + + const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); + if (pos) { + bidObject.pos = pos; + } + + const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); + if (gpid) { + bidObject.gpid = gpid; + } + + const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); + if (placementId) { + bidObject.placementId = placementId; + } + + const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); + if (mimes) { + bidObject.mimes = mimes; + } + + const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); + if (api) { + bidObject.api = api; + } + + const sua = deepAccess(bid, `ortb2.device.sua`); + if (sua) { + bidObject.sua = sua; + } + + const coppa = deepAccess(bid, `ortb2.regs.coppa`); + if (coppa) { + bidObject.coppa = 1; + } + + if (mediaType === VIDEO) { + const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); + let playbackMethodValue; + + if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { + playbackMethodValue = playbackMethod[0]; + } else if (isInteger(playbackMethod)) { + playbackMethodValue = playbackMethod; + } + + if (playbackMethodValue) { + bidObject.playbackMethod = playbackMethodValue; + } + + const placement = deepAccess(bid, `mediaTypes.video.placement`); + if (placement) { + bidObject.placement = placement; + } + + const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); + if (minDuration) { + bidObject.minDuration = minDuration; + } + + const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); + if (maxDuration) { + bidObject.maxDuration = maxDuration; + } + + const skip = deepAccess(bid, `mediaTypes.video.skip`); + if (skip) { + bidObject.skip = skip; + } + + const linearity = deepAccess(bid, `mediaTypes.video.linearity`); + if (linearity) { + bidObject.linearity = linearity; + } + + const protocols = deepAccess(bid, `mediaTypes.video.protocols`); + if (protocols) { + bidObject.protocols = protocols; + } + + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); + if (plcmt) { + bidObject.plcmt = plcmt; + } + } + + return bidObject; +} + +export function buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER) { + const bidResponse = { + requestId: adUnit.requestId, + cpm: adUnit.cpm, + currency: adUnit.currency || DEFAULT_CURRENCY, + width: adUnit.width, + height: adUnit.height, + ttl: adUnit.ttl || TTL, + creativeId: adUnit.creativeId, + netRevenue: adUnit.netRevenue || true, + nurl: adUnit.nurl, + mediaType: adUnit.mediaType, + meta: { + mediaType: adUnit.mediaType + } + }; + + if (adUnit.mediaType === VIDEO) { + bidResponse.vastXml = adUnit.vastXml; + } else if (adUnit.mediaType === BANNER) { + bidResponse.ad = adUnit.ad; + } + + if (adUnit.adomain && adUnit.adomain.length) { + bidResponse.meta.advertiserDomains = adUnit.adomain; + } + + return bidResponse; +} + +function isBanner(bid) { + return bid.mediaTypes && bid.mediaTypes.banner; +} + +export function generateGeneralParams(generalObject, bidderRequest, adapterVersion) { + const domain = window.location.hostname; + const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; + const { bidderCode } = bidderRequest; + const generalBidParams = generalObject.params; + const timeout = bidderRequest.timeout; + const adapVer = adapterVersion || '6.0.0'; + + const generalParams = { + wrapper_type: 'prebidjs', + wrapper_vendor: '$$PREBID_GLOBAL$$', + wrapper_version: '$prebid.version$', + adapter_version: adapVer, + auction_start: bidderRequest.auctionStart, + publisher_id: generalBidParams.org, + publisher_name: domain, + site_domain: domain, + dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, + device_type: getDeviceType(navigator.userAgent), + ua: navigator.userAgent, + is_wrapper: !!generalBidParams.isWrapper, + session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), + tmax: timeout + }; + + const userIdsParam = getBidIdParameter('userId', generalObject); + if (userIdsParam) { + generalParams.userIds = JSON.stringify(userIdsParam); + } + + const ortb2Metadata = bidderRequest.ortb2 || {}; + if (ortb2Metadata.site) { + generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); + } + if (ortb2Metadata.user) { + generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); + } + + if (syncEnabled) { + const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); + if (allowedSyncMethod) { + generalParams.cs_method = allowedSyncMethod; + } + } + + if (bidderRequest.uspConsent) { + generalParams.us_privacy = bidderRequest.uspConsent; + } + + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { + generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; + generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; + } + + if (bidderRequest.gppConsent) { + generalParams.gpp = bidderRequest.gppConsent.gppString; + generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + generalParams.gpp = bidderRequest.ortb2.regs.gpp; + generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (generalBidParams.ifa) { + generalParams.ifa = generalBidParams.ifa; + } + + if (generalObject.schain) { + generalParams.schain = getSupplyChain(generalObject.schain); + } + + if (bidderRequest && bidderRequest.refererInfo) { + generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); + generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); + generalParams.site_domain = deepAccess(bidderRequest, 'refererInfo.domain') || deepAccess(window, 'location.hostname'); + } + + return generalParams; +} diff --git a/libraries/schainSerializer/schainSerializer.js b/libraries/schainSerializer/schainSerializer.js new file mode 100644 index 00000000000..7d9a3c4ddc6 --- /dev/null +++ b/libraries/schainSerializer/schainSerializer.js @@ -0,0 +1,24 @@ +/** + * Serialize the SupplyChain for Non-OpenRTB Requests + * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/supplychainobject.md + * + * @param {Object} schain The supply chain object. + * @param {string} schain.ver The version of the supply chain. + * @param {number} schain.complete Indicates if the chain is complete (1) or not (0). + * @param {Array} schain.nodes An array of nodes in the supply chain. + * @param {Array} nodesProperties The list of node properties to include in the serialized string. + * Can include: 'asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext'. + * @returns {string|null} The serialized supply chain string or null if the nodes are not present. + */ +export function serializeSupplyChain(schain, nodesProperties) { + if (!schain?.nodes) return null; + + const header = `${schain.ver},${schain.complete}!`; + const nodes = schain.nodes.map( + node => nodesProperties.map( + prop => node[prop] ? encodeURIComponent(node[prop]).replace(/!/g, '%21') : '' + ).join(',') + ).join('!'); + + return header + nodes; +} diff --git a/libraries/sizeUtils/tranformSize.js b/libraries/sizeUtils/tranformSize.js new file mode 100644 index 00000000000..1746ac74fb3 --- /dev/null +++ b/libraries/sizeUtils/tranformSize.js @@ -0,0 +1,43 @@ +import * as utils from '../../src/utils.js'; + +/** + * get sizes for rtb + * @param {Array|Object} requestSizes + * @return {Object} + */ +export 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; +} + +export const normalAdSize = [ + { w: 300, h: 250 }, + { w: 300, h: 600 }, + { w: 728, h: 90 }, + { w: 970, h: 250 }, + { w: 320, h: 50 }, + { w: 160, h: 600 }, + { w: 320, h: 180 }, + { w: 320, h: 100 }, + { w: 336, h: 280 }, +]; diff --git a/libraries/smartyadsUtils/getAdUrlByRegion.js b/libraries/smartyadsUtils/getAdUrlByRegion.js new file mode 100644 index 00000000000..cad9055f671 --- /dev/null +++ b/libraries/smartyadsUtils/getAdUrlByRegion.js @@ -0,0 +1,32 @@ +const adUrls = { + US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', + SGP: 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' +}; + +export function getAdUrlByRegion(bid) { + let adUrl; + + if (bid.params.region && adUrls[bid.params.region]) { + adUrl = adUrls[bid.params.region]; + } else { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + adUrl = adUrls['EU']; + break; + case 'Asia': + adUrl = adUrls['SGP']; + break; + default: adUrl = adUrls['US_EAST']; + } + } catch (err) { + adUrl = adUrls['US_EAST']; + } + } + + return adUrl; +}; diff --git a/libraries/targetVideoUtils/bidderUtils.js b/libraries/targetVideoUtils/bidderUtils.js new file mode 100644 index 00000000000..cf106566944 --- /dev/null +++ b/libraries/targetVideoUtils/bidderUtils.js @@ -0,0 +1,213 @@ +import {SYNC_URL} from './constants.js'; +import {VIDEO} from '../../src/mediaTypes.js'; +import {getRefererInfo} from '../../src/refererDetection.js'; +import {createTrackPixelHtml, deepAccess, getBidRequest, formatQS} from '../../src/utils.js'; + +export function getSizes(request) { + let sizes = request.sizes; + if (!sizes && request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { + sizes = request.mediaTypes.banner.sizes; + } + if (Array.isArray(sizes) && !Array.isArray(sizes[0])) { + sizes = [sizes[0], sizes[1]]; + } + if (!Array.isArray(sizes) || !Array.isArray(sizes[0])) { + sizes = [[0, 0]]; + } + + return sizes; +} + +export function formatRequest({payload, url, bidderRequest, bidId}) { + const request = { + method: 'POST', + data: JSON.stringify(payload), + url, + options: { + withCredentials: true, + } + } + + if (bidderRequest) { + request.bidderRequest = bidderRequest; + } + + if (bidId) { + request.bidId = bidId; + } + + return request; +} + +export function createVideoTag(bid) { + const tag = {}; + tag.id = parseInt(bid.params.placementId, 10); + tag.gpid = 'targetVideo'; + tag.sizes = getSizes(bid); + tag.primary_size = tag.sizes[0]; + tag.ad_types = [VIDEO]; + tag.uuid = bid.bidId; + tag.allow_smaller_sizes = false; + tag.use_pmt_rule = false; + tag.prebid = true; + tag.disable_psa = true; + tag.hb_source = 1; + tag.require_asset_url = true; + tag.video = { + playback_method: 2, + skippable: true + }; + + return tag; +} + +export function bannerBid(serverBid, rtbBid, bidderRequest, margin) { + const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); + const sizes = getSizes(bidRequest); + const bid = { + requestId: serverBid.uuid, + cpm: rtbBid.cpm / margin, + creativeId: rtbBid.creative_id, + dealId: rtbBid.deal_id, + currency: 'USD', + netRevenue: true, + width: sizes[0][0], + height: sizes[0][1], + ttl: 300, + adUnitCode: bidRequest.adUnitCode, + appnexus: { + buyerMemberId: rtbBid.buyer_member_id, + dealPriority: rtbBid.deal_priority, + dealCode: rtbBid.deal_code + } + }; + + if (rtbBid.rtb.video) { + Object.assign(bid, { + vastImpUrl: rtbBid.notify_url, + ad: getBannerHtml(rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url)), + ttl: 3600 + }); + } + + return bid; +} + +export function videoBid(serverBid, requestId, currency, params, ttl) { + const {ad, adUrl, vastUrl, vastXml} = getAd(serverBid); + + const bid = { + requestId, + params, + currency, + cpm: serverBid.price, + width: serverBid.w, + height: serverBid.h, + creativeId: serverBid.adid || serverBid.crid, + netRevenue: false, + ttl, + meta: { + advertiserDomains: serverBid.adomain || [] + } + }; + + if (vastUrl || vastXml) { + bid.mediaType = VIDEO; + if (vastUrl) bid.vastUrl = vastUrl; + if (vastXml) bid.vastXml = vastXml; + } else { + bid.ad = ad; + bid.adUrl = adUrl; + }; + + return bid; +} + +export function getRtbBid(tag) { + return tag && tag.ads && tag.ads.length && tag.ads.find(ad => ad.rtb); +} + +export function getBannerHtml(vastUrl) { + return ` + + + + + + + +
+ + + + `; +} + +export function getAd(bid) { + let ad, adUrl, vastXml, vastUrl; + + switch (deepAccess(bid, 'ext.prebid.type')) { + case VIDEO: + if (bid.adm.substr(0, 4) === 'http') { + vastUrl = bid.adm; + } else { + vastXml = bid.adm; + }; + break; + default: + if (bid.adm && bid.nurl) { + ad = bid.adm; + ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); + } else if (bid.adm) { + ad = bid.adm; + } else if (bid.nurl) { + adUrl = bid.nurl; + }; + } + + return {ad, adUrl, vastXml, vastUrl}; +} + +export function getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, endpoint) { + const params = { + endpoint + }; + + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + params.gdpr = (gdprConsent.gdprApplies ? 1 : 0); + params.gdpr_consent = encodeURIComponent(gdprConsent.consentString || ''); + } + + // CCPA + if (uspConsent && typeof uspConsent === 'string') { + params.us_privacy = encodeURIComponent(uspConsent); + } + + // GPP Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params.gpp = encodeURIComponent(gppConsent.gppString); + params.gpp_sid = encodeURIComponent(gppConsent?.applicableSections?.join(',')); + } + + const queryParams = Object.keys(params).length > 0 ? formatQS(params) : ''; + let response = []; + if (syncOptions.iframeEnabled) { + response = [{ + type: 'iframe', + url: SYNC_URL + 'load-cookie.html?' + queryParams + }]; + } + + return response; +} + +export function getSiteObj() { + const refInfo = (getRefererInfo && getRefererInfo()) || {}; + + return { + page: refInfo.page, + ref: refInfo.ref, + domain: refInfo.domain + } +} diff --git a/libraries/targetVideoUtils/constants.js b/libraries/targetVideoUtils/constants.js new file mode 100644 index 00000000000..33076b71e7d --- /dev/null +++ b/libraries/targetVideoUtils/constants.js @@ -0,0 +1,25 @@ +const SOURCE = 'pbjs'; +const GVLID = 786; +const MARGIN = 1.35; +const BIDDER_CODE = 'targetVideo'; + +const TIME_TO_LIVE = 300; +const BANNER_ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; +const VIDEO_ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; +const SYNC_URL = 'https://bppb.link/static/'; +const VIDEO_PARAMS = [ + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', + 'plcmt', 'playbackmethod', 'protocols', 'startdelay', 'placement' +]; + +export { + SOURCE, + GVLID, + MARGIN, + BIDDER_CODE, + SYNC_URL, + TIME_TO_LIVE, + BANNER_ENDPOINT_URL, + VIDEO_ENDPOINT_URL, + VIDEO_PARAMS +} diff --git a/libraries/teqblazeUtils/bidderUtils.js b/libraries/teqblazeUtils/bidderUtils.js new file mode 100644 index 00000000000..f9484ebe5d1 --- /dev/null +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -0,0 +1,261 @@ +import { BANNER, NATIVE, VIDEO } from '../../src/mediaTypes.js'; +import { deepAccess } from '../../src/utils.js'; +import { config } from '../../src/config.js'; + +const PROTOCOL_PATTERN = /^[a-z0-9.+-]+:/i; + +const 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; + } +}; + +const getBidFloor = (bid) => { + try { + const bidFloor = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*', + }); + + return bidFloor?.floor; + } catch (err) { + return 0; + } +}; + +const createBasePlacement = (bid) => { + const { bidId, mediaTypes, transactionId, userIdAsEids } = bid; + const schain = bid.schain || {}; + const bidfloor = getBidFloor(bid); + + const placement = { + 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.plcmt = mediaTypes[VIDEO].plcmt; + 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; + } + + if (transactionId) { + placement.ext = placement.ext || {}; + placement.ext.tid = transactionId; + } + + if (userIdAsEids && userIdAsEids.length) { + placement.eids = userIdAsEids; + } + + return placement; +}; + +const defaultPlacementType = (bid, bidderRequest, placement) => { + const { placementId, endpointId } = bid.params; + + if (placementId) { + placement.placementId = placementId; + placement.type = 'publisher'; + } else if (endpointId) { + placement.endpointId = endpointId; + placement.type = 'network'; + } +}; + +const checkIfObjectHasKey = (keys, obj, mode = 'some') => { + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = obj[key]; + + if (mode === 'some' && val) return true; + if (mode === 'every' && !val) return false; + } + + return mode === 'every'; +} + +export const isBidRequestValid = (keys = ['placementId', 'endpointId'], mode) => (bid = {}) => { + const { params, bidId, mediaTypes } = bid; + let valid = Boolean(bidId && params && checkIfObjectHasKey(keys, params, mode)); + + 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; +}; + +/** + * @param {{ adUrl, validBidRequests, bidderRequest, placementProcessingFunction }} config + * @returns {function} + */ +export const buildRequestsBase = (config) => { + const { adUrl, validBidRequests, bidderRequest } = config; + const placementProcessingFunction = config.placementProcessingFunction || buildPlacementProcessingFunction(); + const device = deepAccess(bidderRequest, 'ortb2.device'); + const page = deepAccess(bidderRequest, 'refererInfo.page', ''); + + const proto = PROTOCOL_PATTERN.exec(page); + const protocol = proto?.[0]; + + const placements = []; + const request = { + deviceWidth: device?.w || 0, + deviceHeight: device?.h || 0, + language: device?.language?.split('-')[0] || '', + secure: protocol === 'https:' ? 1 : 0, + host: deepAccess(bidderRequest, 'refererInfo.domain', ''), + page, + placements, + coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, + tmax: bidderRequest.timeout + }; + + if (bidderRequest.uspConsent) { + request.ccpa = bidderRequest.uspConsent; + } + + if (bidderRequest.gdprConsent) { + request.gdpr = { + consentString: bidderRequest.gdprConsent.consentString + }; + } + + if (bidderRequest.gppConsent) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (bidderRequest?.ortb2?.device) { + request.device = bidderRequest.ortb2.device; + } + + const len = validBidRequests.length; + for (let i = 0; i < len; i++) { + const bid = validBidRequests[i]; + placements.push(placementProcessingFunction(bid, bidderRequest)); + } + + return { + method: 'POST', + url: adUrl, + data: request + }; +}; + +export const buildRequests = (adUrl) => (validBidRequests = [], bidderRequest = {}) => { + const placementProcessingFunction = buildPlacementProcessingFunction(); + + return buildRequestsBase({ adUrl, validBidRequests, bidderRequest, placementProcessingFunction }); +}; + +export function interpretResponseBuilder({addtlBidValidation = (bid) => true} = {}) { + return function (serverResponse) { + let response = []; + for (let i = 0; i < serverResponse.body.length; i++) { + let resItem = serverResponse.body[i]; + if (isBidResponseValid(resItem) && addtlBidValidation(resItem)) { + const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; + resItem.meta = { ...resItem.meta, advertiserDomains }; + + response.push(resItem); + } + } + + return response; + } +} + +export const interpretResponse = interpretResponseBuilder(); + +export const getUserSyncs = (syncUrl) => (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + const type = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let url = syncUrl + `/${type}?pbjs=1`; + + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + url += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + url += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; + } + } + + if (uspConsent && uspConsent.consentString) { + url += `&ccpa_consent=${uspConsent.consentString}`; + } + + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + url += '&gpp=' + gppConsent.gppString; + url += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + + const coppa = config.getConfig('coppa') ? 1 : 0; + url += `&coppa=${coppa}`; + + return [{ + type, + url + }]; +}; + +/** + * + * @param {{ addPlacementType?: function, addCustomFieldsToPlacement?: function }} [config] + * @returns {function(object, object): object} + */ +export const buildPlacementProcessingFunction = (config) => (bid, bidderRequest) => { + const addPlacementType = config?.addPlacementType ?? defaultPlacementType; + + const placement = createBasePlacement(bid); + + addPlacementType(bid, bidderRequest, placement); + + if (config?.addCustomFieldsToPlacement) { + config.addCustomFieldsToPlacement(bid, bidderRequest, placement); + } + + return placement; +}; diff --git a/libraries/timeoutQueue/timeoutQueue.js b/libraries/timeoutQueue/timeoutQueue.js new file mode 100644 index 00000000000..5046eed150b --- /dev/null +++ b/libraries/timeoutQueue/timeoutQueue.js @@ -0,0 +1,22 @@ +export function timeoutQueue() { + const queue = []; + return { + submit(timeout, onResume, onTimeout) { + const item = [ + onResume, + setTimeout(() => { + queue.splice(queue.indexOf(item), 1); + onTimeout(); + }, timeout) + ]; + queue.push(item); + }, + resume() { + while (queue.length) { + const [onResume, timerId] = queue.shift(); + clearTimeout(timerId); + onResume(); + } + } + } +} diff --git a/libraries/userAgentUtils/index.js b/libraries/userAgentUtils/index.js new file mode 100644 index 00000000000..7300bbd519a --- /dev/null +++ b/libraries/userAgentUtils/index.js @@ -0,0 +1,58 @@ +import { deviceTypes, browserTypes, osTypes } from './userAgentTypes.enums.js'; + +/** + * Get the approximate device type enum from the user agent + * @returns {number} + */ +export const getDeviceType = () => { + if ( + /ipad|android 3.0|xoom|sch-i800|playbook|tablet|kindle/i.test( + navigator.userAgent.toLowerCase() + ) + ) return deviceTypes.TABLET; + if ( + /iphone|ipod|android|blackberry|opera|mini|windows\sce|palm|smartphone|iemobile/i.test( + navigator.userAgent.toLowerCase() + ) + ) return deviceTypes.MOBILE; + return deviceTypes.DESKTOP; +}; + +/** + * Get the approximate browser type enum from the user agent (or vendor + * if available) + * @returns {number} + */ +export const getBrowser = () => { + if (/Edg/.test(navigator.userAgent)) return browserTypes.EDGE; + else if ( + /Chrome/.test(navigator.userAgent) && + /Google Inc/.test(navigator.vendor) + ) return browserTypes.CHROME; + else if (navigator.userAgent.match('CriOS')) return browserTypes.CHROME; + else if (/Firefox/.test(navigator.userAgent)) return browserTypes.FIREFOX; + else if ( + /Safari/.test(navigator.userAgent) && + /Apple Computer/.test(navigator.vendor) + ) return browserTypes.SAFARI; + else if ( + /Trident/.test(navigator.userAgent) || + /MSIE/.test(navigator.userAgent) + ) return browserTypes.INTERNET_EXPLORER; + else return browserTypes.OTHER; +}; + +/** + * Get the approximate OS enum from the user agent (or app version, + * if available) + * @returns {number} + */ +export const getOS = () => { + if (navigator.userAgent.indexOf('Android') != -1) return osTypes.ANDROID; + if (navigator.userAgent.indexOf('like Mac') != -1) return osTypes.IOS; + if (navigator.userAgent.indexOf('Win') != -1) return osTypes.WINDOWS; + if (navigator.userAgent.indexOf('Mac') != -1) return osTypes.MAC; + if (navigator.userAgent.indexOf('Linux') != -1) return osTypes.LINUX; + if (navigator.appVersion.indexOf('X11') != -1) return osTypes.UNIX; + return osTypes.OTHER; +}; diff --git a/libraries/userAgentUtils/userAgentTypes.enums.js b/libraries/userAgentUtils/userAgentTypes.enums.js new file mode 100644 index 00000000000..8a0255e88bf --- /dev/null +++ b/libraries/userAgentUtils/userAgentTypes.enums.js @@ -0,0 +1,22 @@ +export const deviceTypes = Object.freeze({ + DESKTOP: 0, + MOBILE: 1, + TABLET: 2, +}) +export const browserTypes = Object.freeze({ + CHROME: 0, + FIREFOX: 1, + SAFARI: 2, + EDGE: 3, + INTERNET_EXPLORER: 4, + OTHER: 5 +}) +export const osTypes = Object.freeze({ + WINDOWS: 0, + MAC: 1, + LINUX: 2, + UNIX: 3, + IOS: 4, + ANDROID: 5, + OTHER: 6 +}) diff --git a/libraries/userSyncUtils/userSyncUtils.js b/libraries/userSyncUtils/userSyncUtils.js new file mode 100644 index 00000000000..db8c77d307b --- /dev/null +++ b/libraries/userSyncUtils/userSyncUtils.js @@ -0,0 +1,24 @@ +export function getUserSyncParams(gdprConsent, uspConsent, gppConsent) { + let params = {}; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + params['gdpr'] = Number(gdprConsent.gdprApplies); + } + + if (typeof gdprConsent.consentString === 'string') { + params['gdpr_consent'] = gdprConsent.consentString; + } + } + + if (uspConsent) { + params['us_privacy'] = encodeURIComponent(uspConsent); + } + + if (gppConsent?.gppString) { + params['gpp'] = gppConsent.gppString; + params['gpp_sid'] = gppConsent.applicableSections?.toString(); + } + + return params; +} diff --git a/libraries/vastTrackers/vastTrackers.js b/libraries/vastTrackers/vastTrackers.js index b4ae98aba57..b8fc829a89a 100644 --- a/libraries/vastTrackers/vastTrackers.js +++ b/libraries/vastTrackers/vastTrackers.js @@ -4,22 +4,47 @@ import {logError} from '../../src/utils.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_REPORT_ANALYTICS} from '../../src/activities/activities.js'; import {activityParams} from '../../src/activities/activityParams.js'; +import {auctionManager} from '../../src/auctionManager.js'; const vastTrackers = []; +let enabled = false; -addBidResponse.before(function (next, adUnitcode, bidResponse, reject) { - if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { - const vastTrackers = getVastTrackers(bidResponse); - if (vastTrackers) { - bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); - const impTrackers = vastTrackers.get('impressions'); - if (impTrackers) { - bidResponse.vastImpUrl = [].concat(impTrackers).concat(bidResponse.vastImpUrl).filter(t => t); +export function reset() { + vastTrackers.length = 0; +} + +export function enable() { + if (!enabled) { + addBidResponse.before(addTrackersToResponse); + enabled = true; + } +} + +export function disable() { + if (enabled) { + addBidResponse.getHooks({hook: addTrackersToResponse}).remove(); + enabled = false; + } +} + +export function responseHook({index = auctionManager.index} = {}) { + return function addTrackersToResponse(next, adUnitcode, bidResponse, reject) { + if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { + const vastTrackers = getVastTrackers(bidResponse, {index}); + if (vastTrackers) { + bidResponse.vastXml = insertVastTrackers(vastTrackers, bidResponse.vastXml); + const impTrackers = vastTrackers.get('impressions'); + if (impTrackers) { + bidResponse.vastImpUrl = [].concat([...impTrackers]).concat(bidResponse.vastImpUrl).filter(t => t); + } } } + next(adUnitcode, bidResponse, reject); } - next(adUnitcode, bidResponse, reject); -}); +} + +const addTrackersToResponse = responseHook(); +enable(); export function registerVastTrackers(moduleType, moduleName, trackerFn) { if (typeof trackerFn === 'function') { @@ -49,7 +74,7 @@ export function insertVastTrackers(trackers, vastXml) { return vastXml; } -export function getVastTrackers(bid) { +export function getVastTrackers(bid, {index = auctionManager.index}) { let trackers = []; vastTrackers.filter( ({ @@ -58,7 +83,9 @@ export function getVastTrackers(bid) { trackerFn }) => isActivityAllowed(ACTIVITY_REPORT_ANALYTICS, activityParams(moduleType, moduleName)) ).forEach(({trackerFn}) => { - let trackersToAdd = trackerFn(bid); + const auction = index.getAuction(bid).getProperties(); + const bidRequest = index.getBidRequest(bid); + let trackersToAdd = trackerFn(bid, {auction, bidRequest}); trackersToAdd.forEach(trackerToAdd => { if (isValidVastTracker(trackers, trackerToAdd)) { trackers.push(trackerToAdd); diff --git a/libraries/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js new file mode 100644 index 00000000000..df947142a4c --- /dev/null +++ b/libraries/vidazooUtils/bidderUtils.js @@ -0,0 +1,500 @@ +import { + _each, + deepAccess, + formatQS, + isArray, + isFn, + parseSizesInput, + parseUrl, + triggerPixel, + uniques +} from '../../src/utils.js'; +import {chunk} from '../chunk/chunk.js'; +import {CURRENCY, DEAL_ID_EXPIRY, SESSION_ID_KEY, TTL_SECONDS, UNIQUE_DEAL_ID_EXPIRY} from './constants.js'; +import {bidderSettings} from '../../src/bidderSettings.js'; +import {config} from '../../src/config.js'; +import {BANNER, VIDEO} from '../../src/mediaTypes.js'; + +export function createSessionId() { + return 'wsid_' + parseInt(Date.now() * Math.random()); +} + +export function getTopWindowQueryParams() { + try { + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + return parsedUrl.search; + } catch (e) { + return ''; + } +} + +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; +} + +export function isBidRequestValid(bid) { + const params = bid.params || {}; + return !!(extractCID(params) && extractPID(params)); +} + +export function tryParseJSON(value) { + try { + return JSON.parse(value); + } catch (e) { + return value; + } +} + +export function setStorageItem(storage, key, value, timestamp) { + try { + const created = timestamp || Date.now(); + const data = JSON.stringify({value, created}); + storage.setDataInLocalStorage(key, data); + } catch (e) { + } +} + +export function getStorageItem(storage, key) { + try { + return tryParseJSON(storage.getDataFromLocalStorage(key, null)); + } catch (e) { + } + + return null; +} + +export function getCacheOpt(storage, useKey) { + let data = storage.getDataFromLocalStorage(useKey, null); + if (!data) { + data = String(Date.now()); + storage.setDataInLocalStorage(useKey, data, null); + } + + return data; +} + +export function getUniqueDealId(storage, key, expiry = UNIQUE_DEAL_ID_EXPIRY) { + const storageKey = `u_${key}`; + const now = Date.now(); + const data = getStorageItem(storage, storageKey); + let uniqueId; + + if (!data || !data.value || now - data.created > expiry) { + uniqueId = `${key}_${now.toString()}`; + setStorageItem(storage, storageKey, uniqueId); + } else { + uniqueId = data.value; + } + + return uniqueId; +} + +export function getNextDealId(storage, key, expiry = DEAL_ID_EXPIRY) { + try { + const data = getStorageItem(storage, 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(storage, key, nextValue, timestamp); + return nextValue; + } catch (e) { + return 0; + } +} + +export function hashCode(s, prefix = '_') { + const l = s.length; + let h = 0 + let i = 0; + if (l > 0) { + while (i < l) { + h = (h << 5) - h + s.charCodeAt(i++) | 0; + } + } + return prefix + h; +} + +export function onBidWon(bid) { + if (!bid.nurl) { + return; + } + const wonBid = { + adId: bid.adId, + creativeId: bid.creativeId, + auctionId: bid.auctionId, + transactionId: bid.transactionId, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + currency: bid.currency, + originalCpm: bid.originalCpm, + originalCurrency: bid.originalCurrency, + netRevenue: bid.netRevenue, + mediaType: bid.mediaType, + timeToRespond: bid.timeToRespond, + status: bid.status, + }; + const qs = formatQS(wonBid); + const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; + triggerPixel(url); +} + +/** + * Create the spec function for getting user syncs + * + * The options object accepts the following fields: + * + * - iframeSyncUrl + * - imageSyncUrl + * + * @param options + */ +export function createUserSyncGetter(options = { + iframeSyncUrl: '', + imageSyncUrl: '' +}) { + return function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { + const syncs = []; + const {iframeEnabled, pixelEnabled} = syncOptions; + const {gdprApplies, consentString = ''} = gdprConsent; + const {gppString, applicableSections} = gppConsent; + + const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); + let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; + + if (gppString && applicableSections?.length) { + params += '&gpp=' + encodeURIComponent(gppString); + params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); + } + + if (iframeEnabled && options.iframeSyncUrl) { + syncs.push({ + type: 'iframe', + url: `${options.iframeSyncUrl}/${params}` + }); + } + if (pixelEnabled && options.imageSyncUrl) { + syncs.push({ + type: 'image', + url: `${options.imageSyncUrl}/${params}` + }); + } + return syncs; + } +} + +export function appendUserIdsToRequestPayload(payloadRef, userIds) { + let key; + _each(userIds, (userId, idSystemProviderName) => { + key = `uid.${idSystemProviderName}`; + + switch (idSystemProviderName) { + case 'lipb': + payloadRef[key] = userId.lipbid; + break; + case 'id5id': + payloadRef[key] = userId.uid; + break; + default: + payloadRef[key] = userId; + } + }); +} + +export function getVidazooSessionId(storage) { + return getStorageItem(storage, SESSION_ID_KEY) || ''; +} + +export function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, getUniqueRequestData) { + const { + params, + bidId, + userId, + adUnitCode, + schain, + mediaTypes, + ortb2Imp, + bidderRequestId, + bidRequestsCount, + bidderRequestsCount, + bidderWinsCount + } = bid; + const {ext} = params; + let {bidFloor} = params; + const hashUrl = hashCode(topWindowUrl); + const uniqueRequestData = isFn(getUniqueRequestData) ? getUniqueRequestData(hashUrl, bid) : {}; + const uniqueDealId = getUniqueDealId(storage, hashUrl); + const pId = extractPID(params); + const isStorageAllowed = bidderSettings.get(bidderCode, 'storageAllowed'); + + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); + const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); + const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); + const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); + const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); + const contentLang = deepAccess(bidderRequest, 'ortb2.site.content.language') || document.documentElement.lang; + const coppa = deepAccess(bidderRequest, 'ortb2.regs.coppa', 0); + + if (isFn(bid.getFloor)) { + const floorInfo = bid.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (floorInfo?.currency === 'USD') { + bidFloor = floorInfo.floor; + } + } + + let data = { + url: encodeURIComponent(topWindowUrl), + uqs: getTopWindowQueryParams(), + cb: Date.now(), + bidFloor: bidFloor, + bidId: bidId, + referrer: bidderRequest.refererInfo.ref, + adUnitCode: adUnitCode, + publisherId: pId, + sizes: sizes, + uniqueDealId: uniqueDealId, + bidderVersion: bidderVersion, + prebidVersion: '$prebid.version$', + res: `${screen.width}x${screen.height}`, + schain: schain, + mediaTypes: mediaTypes, + isStorageAllowed: isStorageAllowed, + gpid: gpid, + cat: cat, + contentData, + contentLang, + coppa, + userData: userData, + pagecat: pagecat, + transactionId: ortb2Imp?.ext?.tid, + bidderRequestId: bidderRequestId, + bidRequestsCount: bidRequestsCount, + bidderRequestsCount: bidderRequestsCount, + bidderWinsCount: bidderWinsCount, + bidderTimeout: bidderTimeout, + ...uniqueRequestData + }; + + appendUserIdsToRequestPayload(data, userId); + + const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); + + if (sua) { + data.sua = sua; + } + + if (bidderRequest.gdprConsent) { + if (bidderRequest.gdprConsent.consentString) { + data.gdprConsent = bidderRequest.gdprConsent.consentString; + } + if (bidderRequest.gdprConsent.gdprApplies !== undefined) { + data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; + } + } + if (bidderRequest.uspConsent) { + data.usPrivacy = bidderRequest.uspConsent; + } + + if (bidderRequest.gppConsent) { + data.gppString = bidderRequest.gppConsent.gppString; + data.gppSid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest.ortb2?.regs?.gpp) { + data.gppString = bidderRequest.ortb2.regs.gpp; + data.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + if (bidderRequest.paapi?.enabled) { + const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); + if (fledge) { + data.fledge = fledge; + } + } + + const api = deepAccess(mediaTypes, 'video.api', []); + if (api.includes(7)) { + const sourceExt = deepAccess(bidderRequest, 'ortb2.source.ext'); + if (sourceExt?.omidpv) { + data.omidpv = sourceExt.omidpv; + } + if (sourceExt?.omidpn) { + data.omidpn = sourceExt.omidpn; + } + } + + const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); + if (dsa) { + data.dsa = dsa; + } + + _each(ext, (value, key) => { + data['ext.' + key] = value; + }); + + return data; +} + +export function createInterpretResponseFn(bidderCode, allowSingleRequest) { + return function interpretResponse(serverResponse, request) { + if (!serverResponse || !serverResponse.body) { + return []; + } + + const singleRequestMode = allowSingleRequest && config.getConfig(`${bidderCode}.singleRequest`); + const reqBidId = deepAccess(request, 'data.bidId'); + const {results} = serverResponse.body; + + let output = []; + + try { + results.forEach((result, i) => { + const { + creativeId, + ad, + price, + exp, + width, + height, + currency, + bidId, + nurl, + advertiserDomains, + metaData, + mediaType = BANNER + } = result; + if (!ad || !price) { + return; + } + + const response = { + requestId: (singleRequestMode && bidId) ? bidId : reqBidId, + cpm: price, + width: width, + height: height, + creativeId: creativeId, + currency: currency || CURRENCY, + netRevenue: true, + ttl: exp || TTL_SECONDS, + }; + + if (nurl) { + response.nurl = nurl; + } + + if (metaData) { + Object.assign(response, { + meta: metaData + }) + } else { + Object.assign(response, { + meta: { + advertiserDomains: advertiserDomains || [] + } + }) + } + + if (mediaType === BANNER) { + Object.assign(response, { + ad: ad, + }); + } else { + Object.assign(response, { + vastXml: ad, + mediaType: VIDEO + }); + } + output.push(response); + }); + + return output; + } catch (e) { + return []; + } + } +} + +export function createBuildRequestsFn(createRequestDomain, createUniqueRequestData, storage, bidderCode, bidderVersion, allowSingleRequest) { + function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { + const {params} = bid; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, createUniqueRequestData); + const dto = { + method: 'POST', url: `${createRequestDomain(subDomain)}/prebid/multi/${cId}`, data: data + }; + return dto; + } + + function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { + const {params} = bidRequests[0]; + const cId = extractCID(params); + const subDomain = extractSubDomain(params); + const data = bidRequests.map(bid => { + const sizes = parseSizesInput(bid.sizes); + return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout, storage, bidderVersion, bidderCode, createUniqueRequestData) + }); + const chunkSize = Math.min(20, config.getConfig(`${bidderCode}.chunkSize`) || 10); + + const chunkedData = chunk(data, chunkSize); + return chunkedData.map(chunk => { + return { + method: 'POST', + url: `${createRequestDomain(subDomain)}/prebid/multi/${cId}`, + data: { + bids: chunk + } + }; + }); + } + + return function buildRequests(validBidRequests, bidderRequest) { + const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; + const bidderTimeout = bidderRequest.timeout || config.getConfig('bidderTimeout'); + + const singleRequestMode = allowSingleRequest && config.getConfig(`${bidderCode}.singleRequest`); + + const requests = []; + + if (singleRequestMode) { + // banner bids are sent as a single request + const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); + if (bannerBidRequests.length > 0) { + const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); + requests.push(...singleRequests); + } + + // video bids are sent as a single request for each bid + + const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); + videoBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } else { + validBidRequests.forEach(validBidRequest => { + const sizes = parseSizesInput(validBidRequest.sizes); + const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); + requests.push(request); + }); + } + return requests; + } +} diff --git a/libraries/vidazooUtils/constants.js b/libraries/vidazooUtils/constants.js new file mode 100644 index 00000000000..b1056c15899 --- /dev/null +++ b/libraries/vidazooUtils/constants.js @@ -0,0 +1,7 @@ +export const CURRENCY = 'USD'; +export const TTL_SECONDS = 60 * 5; +export const DEAL_ID_EXPIRY = 1000 * 60 * 15; +export const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; +export const SESSION_ID_KEY = 'vidSid'; +export const OPT_CACHE_KEY = 'vdzwopt'; +export const OPT_TIME_KEY = 'vdzHum'; diff --git a/libraries/video/constants/ortb.js b/libraries/video/constants/ortb.js index d67c8a5f393..86e7b499774 100644 --- a/libraries/video/constants/ortb.js +++ b/libraries/video/constants/ortb.js @@ -1,6 +1,6 @@ /** * @typedef {Object} OrtbParams - * @property {OrtbVideoParamst} video + * @property {OrtbVideoParams} video * @property {OrtbContentParams} content */ @@ -13,7 +13,8 @@ * @property {number} w - Width of the video player in device independent pixels (DIPS). * @property {number} h - Height of the video player in device independent pixels (DIPS). * @property {number|undefined} startdelay - Indicates the offset of the ad placement. - * @property {number|undefined} placement - Placement type for the impression. + * @property {number|undefined} placement - Legacy Placement type for the impression. + * @property {number|undefined} plcmt - Modern placement type for the impression. * @property {number|undefined} linearity - Indicates if the impression must be linear, nonlinear, etc. If omitted, assume all are allowed. * @property {number} skip - Indicates if the player can allow the video to be skipped, where 0 is no, 1 is yes. * @property {number|undefined} skipmin - Only ad creatives with a duration greater than this value can be skippable; only applicable if the ad is skippable. @@ -97,6 +98,18 @@ export const PLACEMENT = { INTERSTITIAL_SLIDER_FLOATING: 5 }; +/** + * ADCOM - https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/develop/AdCOM%20v1.0%20FINAL.md#list_plcmtsubtypesvideo + * @enum OrtbVideoParams.plcmt + */ +export const PLCMT = { + INSTREAM: 1, + ACCOMPANYING_CONTENT: 2, + INTERSTITIAL: 3, + OUTSTREAM: 4, + NO_CONTENT: 4 +}; + /** * ORTB 2.5 section 5.4 - Ad Position * @enum OrtbVideoParams.pos diff --git a/libraries/video/constants/vendorCodes.js b/libraries/video/constants/vendorCodes.js index 370c151b997..4e3550ce431 100644 --- a/libraries/video/constants/vendorCodes.js +++ b/libraries/video/constants/vendorCodes.js @@ -1,6 +1,7 @@ // Video Vendors export const JWPLAYER_VENDOR = 1; export const VIDEO_JS_VENDOR = 2; +export const AD_PLAYER_PRO_VENDOR = 3; // Ad Server Vendors export const GAM_VENDOR = 'gam'; diff --git a/libraries/viewport/viewport.js b/libraries/viewport/viewport.js new file mode 100644 index 00000000000..b385e9a27ec --- /dev/null +++ b/libraries/viewport/viewport.js @@ -0,0 +1,13 @@ +import {getWindowTop} from '../../src/utils.js'; + +export function getViewportCoordinates() { + try { + const win = getWindowTop(); + let { scrollY: top, scrollX: left, innerHeight, innerWidth } = win; + innerHeight = innerHeight || win.document.documentElement.clientWidth || win.document.body.clientWidth; + innerWidth = innerWidth || win.document.documentElement.clientHeight || win.document.body.clientHeight + return { top, right: left + innerWidth, bottom: top + innerHeight, left }; + } catch (e) { + return {}; + } +} diff --git a/libraries/weakStore/weakStore.js b/libraries/weakStore/weakStore.js new file mode 100644 index 00000000000..09606354dae --- /dev/null +++ b/libraries/weakStore/weakStore.js @@ -0,0 +1,15 @@ +import {auctionManager} from '../../src/auctionManager.js'; + +export function weakStore(get) { + const store = new WeakMap(); + return function (id, init = {}) { + const obj = get(id); + if (obj == null) return; + if (!store.has(obj)) { + store.set(obj, init); + } + return store.get(obj); + }; +} + +export const auctionStore = () => weakStore((auctionId) => auctionManager.index.getAuction({auctionId})); diff --git a/libraries/xeUtils/bidderUtils.js b/libraries/xeUtils/bidderUtils.js new file mode 100644 index 00000000000..f03c6323d76 --- /dev/null +++ b/libraries/xeUtils/bidderUtils.js @@ -0,0 +1,158 @@ +import {deepAccess, getBidIdParameter, isFn, logError, isArray, parseSizesInput, isPlainObject} from '../../src/utils.js'; +import {getAdUnitSizes} from '../sizeUtils/sizeUtils.js'; +import {findIndex} from '../../src/polyfill.js'; + +export function getBidFloor(bid, currency = 'USD') { + if (!isFn(bid.getFloor)) { + return null; + } + + let floor = bid.getFloor({ + currency, + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === currency) { + return floor.floor; + } + + return null; +} + +export function isBidRequestValid(bid) { + if (bid && typeof bid.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } + + if (!getBidIdParameter('env', bid.params) || !getBidIdParameter('pid', bid.params)) { + logError('Env or pid is not present in bidder params'); + return false; + } + + if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; + } + + return true; +} + +export function buildRequests(validBidRequests, bidderRequest, endpoint) { + const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; + const requests = validBidRequests.map(req => { + const request = {}; + request.bidId = req.bidId; + request.banner = deepAccess(req, 'mediaTypes.banner'); + request.auctionId = req.ortb2?.source?.tid; + request.transactionId = req.ortb2Imp?.ext?.tid; + request.sizes = parseSizesInput(getAdUnitSizes(req)); + request.schain = req.schain; + request.location = { + page: refererInfo.page, + location: refererInfo.location, + domain: refererInfo.domain, + whost: window.location.host, + ref: refererInfo.ref, + isAmp: refererInfo.isAmp + }; + request.device = { + ua: navigator.userAgent, + lang: navigator.language + }; + request.env = { + env: req.params.env, + pid: req.params.pid + }; + request.ortb2 = req.ortb2; + request.ortb2Imp = req.ortb2Imp; + request.tz = new Date().getTimezoneOffset(); + request.ext = req.params.ext; + request.bc = req.bidRequestsCount; + request.floor = getBidFloor(req); + + if (req.userIdAsEids && req.userIdAsEids.length !== 0) { + request.userEids = req.userIdAsEids; + } else { + request.userEids = []; + } + if (gdprConsent.gdprApplies) { + request.gdprApplies = Number(gdprConsent.gdprApplies); + request.consentString = gdprConsent.consentString; + } else { + request.gdprApplies = 0; + request.consentString = ''; + } + if (uspConsent) { + request.usPrivacy = uspConsent; + } else { + request.usPrivacy = ''; + } + const video = deepAccess(req, 'mediaTypes.video'); + if (video) { + request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); + request.video = video; + } + + return request; + }); + + return { + method: 'POST', + url: endpoint + '/bid', + data: JSON.stringify(requests), + withCredentials: true, + bidderRequest, + options: { + contentType: 'application/json', + } + }; +} + +export function interpretResponse(serverResponse, {bidderRequest}) { + const response = []; + if (!isArray(deepAccess(serverResponse, 'body.data'))) { + return response; + } + + serverResponse.body.data.forEach(serverBid => { + const bidIndex = findIndex(bidderRequest.bids, (bidRequest) => { + return bidRequest.bidId === serverBid.requestId; + }); + + if (bidIndex !== -1) { + const bid = { + requestId: serverBid.requestId, + dealId: bidderRequest.dealId || null, + ...serverBid + }; + response.push(bid); + } + }); + + return response; +} + +export function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { + const syncs = []; + const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); + + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { + const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; + const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; + const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; + + pixels.forEach(pixel => { + const [type, url] = pixel; + const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; + if (type === 'iframe' && syncOptions.iframeEnabled) { + syncs.push(sync) + } else if (type === 'image' && syncOptions.pixelEnabled) { + syncs.push(sync) + } + }); + } + + return syncs; +} diff --git a/module-alias.json b/module-alias.json new file mode 100644 index 00000000000..4e24e2baaba --- /dev/null +++ b/module-alias.json @@ -0,0 +1,4 @@ +{ + "yahoosspBidAdapter": "yahooAdsBidAdapter", + "consentManagement": "consentManagementTcf" +} \ No newline at end of file diff --git a/modules.json b/modules.json index 205b237d0fa..e588bc5660a 100644 --- a/modules.json +++ b/modules.json @@ -10,12 +10,11 @@ "concertBidAdapter", "connectIdSystem", "consentManagementUsp", - "consentManagement", + "consentManagementTcf", "conversantBidAdapter", "criteoBidAdapter", "criteoIdSystem", "dfpAdServerVideo", - "enrichmentFpdModule", "fabrickIdSystem", "gptPreAuction", "gridBidAdapter", @@ -59,7 +58,7 @@ "userId", "vidazooBidAdapter", "videojsVideoProvider", - "yahoosspBidAdapter", + "yahooAdsBidAdapter", "yieldmoBidAdapter", "pulsepointBidAdapter", "pulsepointAnalyticsAdapter", diff --git a/modules/.submodules.json b/modules/.submodules.json index 1eb5253ac12..6dac11bf0ed 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -6,7 +6,6 @@ "adtelligentIdSystem", "adqueryIdSystem", "amxIdSystem", - "britepoolIdSystem", "connectIdSystem", "czechAdIdSystem", "criteoIdSystem", @@ -37,30 +36,34 @@ "novatiqIdSystem", "oneKeyIdSystem", "operaadsIdSystem", - "parrableIdSystem", + "permutiveIdentityManagerIdSystem", "pubProvidedIdSystem", "publinkIdSystem", "quantcastIdSystem", + "rewardedInterestIdSystem", "sharedIdSystem", "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "utiqSystem", + "utiqIdSystem", "utiqMtpIdSystem", "uid2IdSystem", "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "yandexIdSystem" ], "adpod": [ "freeWheelAdserverVideo", - "dfpAdServerVideo" + "dfpAdpod" ], "rtdModule": [ "1plusXRtdProvider", + "51DegreesRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", + "adagioRtdProvider", "adlooxRtdProvider", "adnuntiusRtdProvider", "airgridRtdProvider", @@ -80,6 +83,7 @@ "greenbidsRtdProvider", "growthCodeRtdProvider", "hadronRtdProvider", + "humansecurityRtdProvider", "iasRtdProvider", "idWardRtdProvider", "imRtdProvider", @@ -87,6 +91,7 @@ "jwplayerRtdProvider", "medianetRtdProvider", "mgidRtdProvider", + "mobianRtdProvider", "neuwoRtdProvider", "oneKeyRtdProvider", "optimeraRtdProvider", @@ -99,7 +104,8 @@ "sirdataRtdProvider", "symitriDapRtdProvider", "timeoutRtdProvider", - "weboramaRtdProvider" + "weboramaRtdProvider", + "wurflRtdProvider" ], "fpdModule": [ "validationFpdModule", @@ -107,10 +113,12 @@ ], "videoModule": [ "jwplayerVideoProvider", - "videojsVideoProvider" + "videojsVideoProvider", + "adplayerproVideoProvider" ], "paapi": [ - "fledgeForGpt" + "paapiForGpt", + "topLevelPaapi" ] } } diff --git a/modules/1plusXRtdProvider.js b/modules/1plusXRtdProvider.js index c5c4594ff22..88891b14a78 100644 --- a/modules/1plusXRtdProvider.js +++ b/modules/1plusXRtdProvider.js @@ -1,5 +1,7 @@ import { submodule } from '../src/hook.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; import { ajax } from '../src/ajax.js'; +import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; import { logMessage, logError, deepAccess, deepSetValue, mergeDeep, @@ -13,6 +15,9 @@ const ORTB2_NAME = '1plusX.com' const PAPI_VERSION = 'v1.0'; const LOG_PREFIX = '[1plusX RTD Module]: '; const OPE_FPID = 'ope_fpid' + +export const fpidStorage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME }); + export const segtaxes = { // cf. https://github.com/InteractiveAdvertisingBureau/openrtb/pull/108 AUDIENCE: 526, @@ -53,7 +58,19 @@ export const extractConfig = (moduleConfig, reqBidsConfigObj) => { throw new Error('No bidRequestConfig bidder found in moduleConfig bidders'); } - return { customerId, timeout, bidders }; + const fpidStorageType = deepAccess(moduleConfig, 'params.fpidStorageType', + STORAGE_TYPE_LOCALSTORAGE) + + if ( + fpidStorageType !== STORAGE_TYPE_COOKIES && + fpidStorageType !== STORAGE_TYPE_LOCALSTORAGE + ) { + throw new Error( + `fpidStorageType must be ${STORAGE_TYPE_LOCALSTORAGE} or ${STORAGE_TYPE_COOKIES}` + ) + } + + return { customerId, timeout, bidders, fpidStorageType }; } /** @@ -81,16 +98,20 @@ export const extractConsent = ({ gdpr }) => { } /** - * Extracts the OPE first party id field from local storage + * Extracts the OPE first party id field + * @param {string} fpidStorageType indicates where fpid should be read from * @returns fpid string if found, else null */ -export const extractFpid = () => { +export const extractFpid = (fpidStorageType) => { try { - const fpid = window.localStorage.getItem(OPE_FPID); - if (fpid) { - return fpid; + switch (fpidStorageType) { + case STORAGE_TYPE_COOKIES: return fpidStorage.getCookie(OPE_FPID) + case STORAGE_TYPE_LOCALSTORAGE: return fpidStorage.getDataFromLocalStorage(OPE_FPID) + default: { + logError(`Got unknown fpidStorageType ${fpidStorageType}. Aborting...`) + return null + } } - return null; } catch (error) { return null; } @@ -231,10 +252,10 @@ const init = (config, userConsent) => { const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { try { // Get the required config - const { customerId, bidders } = extractConfig(moduleConfig, reqBidsConfigObj); + const { customerId, bidders, fpidStorageType } = extractConfig(moduleConfig, reqBidsConfigObj); const { ortb2Fragments: { bidder: biddersOrtb2 } } = reqBidsConfigObj; // Get PAPI URL - const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}, extractFpid()) + const papiUrl = getPapiUrl(customerId, extractConsent(userConsent) || {}, extractFpid(fpidStorageType)) // Call PAPI getTargetingDataFromPapi(papiUrl) .then((papiResponse) => { diff --git a/modules/1plusXRtdProvider.md b/modules/1plusXRtdProvider.md index 6a6211b37cc..c1e5a6f48a4 100644 --- a/modules/1plusXRtdProvider.md +++ b/modules/1plusXRtdProvider.md @@ -45,15 +45,16 @@ pbjs.setConfig({ ### Parameters -| Name | Type | Description | Default | -| :---------------- | :------------ | :--------------------------------------------------------------- |:----------------- | -| name | String | Real time data module name | Always '1plusX' | -| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | -| params | Object | | | -| params.customerId | String | Your 1plusX customer id | | -| params.bidders | Array | List of bidders for which you would like data to be set | | -| params.timeout | Integer | timeout (ms) | 1000ms | - +| Name | Type | Description | Default | +| :------------------------ | :------------ | :--------------------------------------------------------------- |:----------------- | +| name | String | Real time data module name | Always '1plusX' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | | +| params.customerId | String | Your 1plusX customer id | | +| params.bidders | Array | List of bidders for which you would like data to be set | | +| params.timeout | Integer | timeout (ms) | 1000ms | +| params.fpidStorageType | String | Specifies where the 1plusX fpid should be read from. Either | html5 | +| | | "html5" (local storage) or "cookie" (first party cookie) | | ## Testing To view an example of how the 1plusX RTD module works : diff --git a/modules/33acrossAnalyticsAdapter.js b/modules/33acrossAnalyticsAdapter.js index 890c963fa71..ec2e669be1a 100644 --- a/modules/33acrossAnalyticsAdapter.js +++ b/modules/33acrossAnalyticsAdapter.js @@ -5,6 +5,7 @@ import adapterManager, { coppaDataHandler, gdprDataHandler, gppDataHandler, uspD * @typedef {typeof import('../src/constants.js').EVENTS} EVENTS */ import { EVENTS } from '../src/constants.js'; +import { sendBeacon } from '../src/ajax.js'; /** @typedef {'pending'|'available'|'targetingSet'|'rendered'|'timeout'|'rejected'|'noBid'|'error'} BidStatus */ /** @@ -629,7 +630,7 @@ function setCachedBidStatus(auctionId, bidId, status) { * @param {string} endpoint URL */ function sendReport(report, endpoint) { - if (navigator.sendBeacon(endpoint, JSON.stringify(report))) { + if (sendBeacon(endpoint, JSON.stringify(report))) { log.info(`Analytics report sent to ${endpoint}`, report); return; diff --git a/modules/33acrossAnalyticsAdapter.md b/modules/33acrossAnalyticsAdapter.md index c56059e5526..d093434dc97 100644 --- a/modules/33acrossAnalyticsAdapter.md +++ b/modules/33acrossAnalyticsAdapter.md @@ -49,7 +49,7 @@ by default when Prebid is downloaded. If you are compiling from source, this might look something like: ```sh -gulp bundle --modules=gptPreAuction,consentManagement,consentManagementGpp,consentManagementUsp,enrichmentFpdModule,gdprEnforcement,33acrossBidAdapter,33acrossIdSystem,33acrossAnalyticsAdapter +gulp bundle --modules=gptPreAuction,consentManagementTcf,consentManagementGpp,consentManagementUsp,tcfControl,33acrossBidAdapter,33acrossIdSystem,33acrossAnalyticsAdapter ``` Enable the 33Across Analytics Adapter in Prebid.js using the analytics provider `33across` diff --git a/modules/33acrossBidAdapter.js b/modules/33acrossBidAdapter.js index 0e9beb22013..555d9c8a291 100644 --- a/modules/33acrossBidAdapter.js +++ b/modules/33acrossBidAdapter.js @@ -15,7 +15,7 @@ import { import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {isSlotMatchingAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -// **************************** UTILS *************************** // +// **************************** UTILS ************************** // const BIDDER_CODE = '33across'; const BIDDER_ALIASES = ['33across_mgni']; const END_POINT = 'https://ssc.33across.com/api/v1/hb'; @@ -36,6 +36,7 @@ const VIDEO_ORTB_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -72,9 +73,7 @@ function isBidRequestValid(bid) { } function _validateBasic(bid) { - const invalidBidderName = bid.bidder !== BIDDER_CODE && !BIDDER_ALIASES.includes(bid.bidder); - - if (invalidBidderName || !bid.params) { + if (!bid.params) { return false; } @@ -140,10 +139,10 @@ function _validateVideo(bid) { } // If placement if defined, it must be a number - if ( - typeof videoParams.placement !== 'undefined' && - typeof videoParams.placement !== 'number' - ) { + if ([ videoParams.placement, videoParams.plcmt ].some(value => ( + typeof value !== 'undefined' && + typeof value !== 'number' + ))) { return false; } @@ -490,12 +489,24 @@ function _buildVideoORTB(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 the old deprecated field is defined, use its value for the recent placement field + + const calculatePlacementValue = () => { + const IN_BANNER_PLACEMENT_VALUE = 2; + + if (video.placement) { + logWarn('[33Across Adapter] The ORTB field `placement` is deprecated, please use `plcmt` instead'); + + return video.placement; + } + + return IN_BANNER_PLACEMENT_VALUE; + } + + video.plcmt ??= calculatePlacementValue(); if (product === PRODUCT.INSTREAM) { video.startdelay = video.startdelay || 0; - video.placement = 1; } // bidfloors @@ -524,7 +535,7 @@ function _getBidFloors(bidRequest, size, mediaType) { size: [ size.w, size.h ] }); - if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { + if (!isNaN(bidFloors?.floor) && (bidFloors?.currency === CURRENCY)) { return bidFloors.floor; } } diff --git a/modules/33acrossIdSystem.js b/modules/33acrossIdSystem.js index e0f7435a1ec..8f99846017a 100644 --- a/modules/33acrossIdSystem.js +++ b/modules/33acrossIdSystem.js @@ -11,6 +11,7 @@ import { submodule } from '../src/hook.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from '../src/adapterManager.js'; import { getStorageManager, STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { domainOverrideToRootDomain } from '../libraries/domainOverrideToRootDomain/index.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -25,9 +26,16 @@ const CALLER_NAME = 'pbjs'; const GVLID = 58; const STORAGE_FPID_KEY = '33acrossIdFp'; +const STORAGE_TPID_KEY = '33acrossIdTp'; +const DEFAULT_1PID_SUPPORT = true; +const DEFAULT_TPID_SUPPORT = true; export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +export const domainUtils = { + domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME) +}; + function calculateResponseObj(response) { if (!response.succeeded) { if (response.error == 'Cookied User') { @@ -46,11 +54,12 @@ function calculateResponseObj(response) { return { envelope: response.data.envelope, - fp: response.data.fp + fp: response.data.fp, + tp: response.data.tp }; } -function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { +function calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes) { const uspString = uspDataHandler.getConsentData(); const coppaValue = coppaDataHandler.getCoppa(); const gppConsent = gppDataHandler.getConsentData(); @@ -78,11 +87,16 @@ function calculateQueryStringParams(pid, gdprConsentData, storageConfig) { params.gdpr_consent = gdprConsentData.consentString; } - const fp = getStoredValue(STORAGE_FPID_KEY, storageConfig); + const fp = getStoredValue(STORAGE_FPID_KEY, enabledStorageTypes); if (fp) { params.fp = encodeURIComponent(fp); } + const tp = getStoredValue(STORAGE_TPID_KEY, enabledStorageTypes); + if (tp) { + params.tp = encodeURIComponent(tp); + } + return params; } @@ -90,35 +104,45 @@ function deleteFromStorage(key) { if (storage.cookiesAreEnabled()) { const expiredDate = new Date(0).toUTCString(); - storage.setCookie(key, '', expiredDate, 'Lax'); + storage.setCookie(key, '', expiredDate, 'Lax', domainUtils.domainOverride()); } storage.removeDataFromLocalStorage(key); } -function storeValue(key, value, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - const expirationInMs = 60 * 60 * 24 * 1000 * storageConfig.expires; - const expirationTime = new Date(Date.now() + expirationInMs); +function storeValue(key, value, { enabledStorageTypes, expires }) { + enabledStorageTypes.forEach(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + const expirationInMs = 60 * 60 * 24 * 1000 * expires; + const expirationTime = new Date(Date.now() + expirationInMs); - storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax'); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - storage.setDataInLocalStorage(key, value); - } + storage.setCookie(key, value, expirationTime.toUTCString(), 'Lax', domainUtils.domainOverride()); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storage.setDataInLocalStorage(key, value); + } + }); } -function getStoredValue(key, storageConfig = {}) { - if (storageConfig.type === STORAGE_TYPE_COOKIES && storage.cookiesAreEnabled()) { - return storage.getCookie(key); - } else if (storageConfig.type === STORAGE_TYPE_LOCALSTORAGE) { - return storage.getDataFromLocalStorage(key); - } +function getStoredValue(key, enabledStorageTypes) { + let storedValue; + + enabledStorageTypes.find(storageType => { + if (storageType === STORAGE_TYPE_COOKIES) { + storedValue = storage.getCookie(key); + } else if (storageType === STORAGE_TYPE_LOCALSTORAGE) { + storedValue = storage.getDataFromLocalStorage(key); + } + + return !!storedValue; + }); + + return storedValue; } -function handleFpId(fpId, storageConfig = {}) { - fpId - ? storeValue(STORAGE_FPID_KEY, fpId, storageConfig) - : deleteFromStorage(STORAGE_FPID_KEY); +function handleSupplementalId(key, id, storageConfig) { + id + ? storeValue(key, id, storageConfig) + : deleteFromStorage(key); } /** @type {Submodule} */ @@ -151,7 +175,7 @@ export const thirthyThreeAcrossIdSubmodule = { * @param {SubmoduleConfig} [config] * @returns {IdResponse|undefined} */ - getId({ params = { }, storage: storageConfig }, gdprConsentData) { + getId({ params = { }, enabledStorageTypes = [], storage: storageConfig = {} }, gdprConsentData) { if (typeof params.pid !== 'string') { logError(`${MODULE_NAME}: Submodule requires a partner ID to be defined`); @@ -164,7 +188,7 @@ export const thirthyThreeAcrossIdSubmodule = { return; } - const { pid, storeFpid, apiUrl = API_URL } = params; + const { pid, storeFpid = DEFAULT_1PID_SUPPORT, storeTpid = DEFAULT_TPID_SUPPORT, apiUrl = API_URL } = params; return { callback(cb) { @@ -183,7 +207,17 @@ export const thirthyThreeAcrossIdSubmodule = { } if (storeFpid) { - handleFpId(responseObj.fp, storageConfig); + handleSupplementalId(STORAGE_FPID_KEY, responseObj.fp, { + enabledStorageTypes, + expires: storageConfig.expires + }); + } + + if (storeTpid) { + handleSupplementalId(STORAGE_TPID_KEY, responseObj.tp, { + enabledStorageTypes, + expires: storageConfig.expires + }); } cb(responseObj.envelope); @@ -193,10 +227,14 @@ export const thirthyThreeAcrossIdSubmodule = { cb(); } - }, calculateQueryStringParams(pid, gdprConsentData, storageConfig), { method: 'GET', withCredentials: true }); + }, calculateQueryStringParams(pid, gdprConsentData, enabledStorageTypes), { + method: 'GET', + withCredentials: true + }); } }; }, + domainOverride: domainUtils.domainOverride, eids: { '33acrossId': { source: '33across.com', diff --git a/modules/33acrossIdSystem.md b/modules/33acrossIdSystem.md index 8b73a43069d..e983c8ab871 100644 --- a/modules/33acrossIdSystem.md +++ b/modules/33acrossIdSystem.md @@ -14,7 +14,7 @@ pbjs.setConfig({ name: "33acrossId", storage: { name: "33acrossId", - type: "html5", + type: "cookie&html5", expires: 30, refreshInSeconds: 8*3600 }, @@ -40,7 +40,7 @@ The following settings are available for the `storage` property in the `userSync | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | name | Required | String| Name of the cookie or HTML5 local storage where the user ID will be stored | `"33acrossId"` | -| type | Required | String | `"html5"` (preferred) or `"cookie"` | `"html5"` | +| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` | | expires | Strongly Recommended | Number | How long (in days) the user ID information will be stored. 33Across recommends `30`. | `30` | | refreshInSeconds | Strongly Recommended | Number | The interval (in seconds) for refreshing the user ID. 33Across recommends no more than 8 hours between refreshes. | `8*3600` | @@ -51,4 +51,5 @@ The following settings are available in the `params` property in `userSync.userI | Param name | Scope | Type | Description | Example | | --- | --- | --- | --- | --- | | pid | Required | String | Partner ID provided by 33Across | `"0010b00002GYU4eBAH"` | -| storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability | `false` (default) or `true` | +| storeFpid | Optional | Boolean | Indicates whether a supplemental first-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | +| storeTpid | Optional | Boolean | Indicates whether a supplemental third-party ID may be stored to improve addressability, this feature is enabled by default | `true` (default) or `false` | diff --git a/modules/51DegreesRtdProvider.js b/modules/51DegreesRtdProvider.js new file mode 100644 index 00000000000..8251fd76e28 --- /dev/null +++ b/modules/51DegreesRtdProvider.js @@ -0,0 +1,335 @@ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import {loadExternalScript} from '../src/adloader.js'; +import {submodule} from '../src/hook.js'; +import { + deepAccess, + deepSetValue, + formatQS, + mergeDeep, + prefixLog, +} from '../src/utils.js'; + +const MODULE_NAME = '51Degrees'; +export const LOG_PREFIX = `[${MODULE_NAME} RTD Submodule]:`; +const {logMessage, logWarn, logError} = prefixLog(LOG_PREFIX); + +// ORTB device types +const ORTB_DEVICE_TYPE = { + UNKNOWN: 0, + MOBILE_TABLET: 1, + PERSONAL_COMPUTER: 2, + CONNECTED_TV: 3, + PHONE: 4, + TABLET: 5, + CONNECTED_DEVICE: 6, + SET_TOP_BOX: 7, + OOH_DEVICE: 8 +}; + +// Map of 51Degrees device types to ORTB device types. See +// https://51degrees.com/developers/property-dictionary?item=Device%7CDevice +// for available properties and values. +const ORTB_DEVICE_TYPE_MAP = new Map([ + ['Phone', ORTB_DEVICE_TYPE.PHONE], + ['Console', ORTB_DEVICE_TYPE.SET_TOP_BOX], + ['Desktop', ORTB_DEVICE_TYPE.PERSONAL_COMPUTER], + ['EReader', ORTB_DEVICE_TYPE.PERSONAL_COMPUTER], + ['IoT', ORTB_DEVICE_TYPE.CONNECTED_DEVICE], + ['Kiosk', ORTB_DEVICE_TYPE.OOH_DEVICE], + ['MediaHub', ORTB_DEVICE_TYPE.SET_TOP_BOX], + ['Mobile', ORTB_DEVICE_TYPE.MOBILE_TABLET], + ['Router', ORTB_DEVICE_TYPE.CONNECTED_DEVICE], + ['SmallScreen', ORTB_DEVICE_TYPE.CONNECTED_DEVICE], + ['SmartPhone', ORTB_DEVICE_TYPE.MOBILE_TABLET], + ['SmartSpeaker', ORTB_DEVICE_TYPE.CONNECTED_DEVICE], + ['SmartWatch', ORTB_DEVICE_TYPE.CONNECTED_DEVICE], + ['Tablet', ORTB_DEVICE_TYPE.TABLET], + ['Tv', ORTB_DEVICE_TYPE.CONNECTED_TV], + ['Vehicle Display', ORTB_DEVICE_TYPE.PERSONAL_COMPUTER] +]); + +/** + * Extracts the parameters for 51Degrees RTD module from the config object passed at instantiation + * @param {Object} moduleConfig Configuration object of the 51Degrees RTD module + * @param {Object} reqBidsConfigObj Configuration object for the bidders, currently not used + */ +export const extractConfig = (moduleConfig, reqBidsConfigObj) => { + // Resource key + let resourceKey = deepAccess(moduleConfig, 'params.resourceKey'); + // On-premise JS URL + let onPremiseJSUrl = deepAccess(moduleConfig, 'params.onPremiseJSUrl'); + + // Trim the values + if (typeof resourceKey === 'string') { + resourceKey = resourceKey.trim(); + } + if (typeof onPremiseJSUrl === 'string') { + onPremiseJSUrl = onPremiseJSUrl.trim(); + } + + // If this module is configured via a 3rd party wrapper, both form inputs + // might be mandatory. To handle this, 0 can be used as a value to skip + // the parameter. + if (typeof resourceKey === 'string' && resourceKey.trim() === '0') { + resourceKey = undefined; + } + if (typeof onPremiseJSUrl === 'string' && onPremiseJSUrl.trim() === '0') { + onPremiseJSUrl = undefined; + } + + // Verify that onPremiseJSUrl is a valid URL: either a full URL, relative + // path (/path/to/file.js), or a protocol-relative URL (//example.com/path/to/file.js) + if (typeof onPremiseJSUrl === 'string' && onPremiseJSUrl.length && !( + onPremiseJSUrl.startsWith('https://') || + onPremiseJSUrl.startsWith('http://') || + onPremiseJSUrl.startsWith('/')) + ) { + throw new Error(LOG_PREFIX + ' Invalid URL format for onPremiseJSUrl in moduleConfig'); + } + + // Verify that one of the parameters is provided, + // but not both at the same time + if (!resourceKey && !onPremiseJSUrl) { + throw new Error(LOG_PREFIX + ' Missing parameter resourceKey or onPremiseJSUrl in moduleConfig'); + } else if (resourceKey && onPremiseJSUrl) { + throw new Error(LOG_PREFIX + ' Only one of resourceKey or onPremiseJSUrl should be provided in moduleConfig'); + } + + // Verify that the resource key is not the one provided as an example + if (resourceKey === '') { + throw new Error(LOG_PREFIX + ' replace in configuration with a resource key obtained from https://configure.51degrees.com/HNZ75HT1'); + } + + return {resourceKey, onPremiseJSUrl}; +} + +/** + * Gets 51Degrees JS URL + * @param {Object} pathData API path data + * @param {string} [pathData.resourceKey] Resource key + * @param {string} [pathData.onPremiseJSUrl] On-premise JS URL + * @param {Object} [pathData.hev] High entropy values + * @param {Window} [win] Window object (mainly for testing) + * @returns {string} 51Degrees JS URL + */ +export const get51DegreesJSURL = (pathData, win) => { + const _window = win || window; + const baseURL = pathData.onPremiseJSUrl || `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`; + + const queryPrefix = baseURL.includes('?') ? '&' : '?'; + const qs = {}; + + deepSetNotEmptyValue( + qs, + '51D_GetHighEntropyValues', + pathData.hev && Object.keys(pathData.hev).length ? btoa(JSON.stringify(pathData.hev)) : null, + ); + deepSetNotEmptyValue(qs, '51D_ScreenPixelsHeight', _window?.screen?.height); + deepSetNotEmptyValue(qs, '51D_ScreenPixelsWidth', _window?.screen?.width); + deepSetNotEmptyValue(qs, '51D_PixelRatio', _window?.devicePixelRatio); + + const _qs = formatQS(qs); + const _qsString = _qs ? `${queryPrefix}${_qs}` : ''; + + return `${baseURL}${_qsString}`; +} + +/** + * Retrieves high entropy values from `navigator.userAgentData` if available + * + * @param {Array} hints - An array of hints indicating which high entropy values to retrieve + * @returns {Promise>} A promise that resolves to an object containing high entropy values if supported, or `undefined` if not + */ +export const getHighEntropyValues = async (hints) => { + return navigator?.userAgentData?.getHighEntropyValues?.(hints); +}; + +/** + * Check if meta[http-equiv="Delegate-CH"] tag is present in the document head and points to 51Degrees cloud + * + * The way to delegate processing User-Agent Client Hints to a 3rd party is either + * via setting Permissions-Policy + Accept-CH response headers or Delegate-CH meta-http equiv. + * Of those two, Delegate-CH meta http-equiv is an easier and more performant option + * (client hints are sent on the very first request without a round trip required). + * Using the getHighEntropyValues() API is an alternative; + * however, Google is likely to restrict it as part of the Privacy Sandbox in future + * versions of Chrome, so we want to be future-proof and transparent here. + * Hence, a check that would output the warning if the user does not have proper delegation of UA-CH. + * + * @returns {boolean} True if 51Degrees meta is present + * @returns {boolean} False if 51Degrees meta is not present + */ +export const is51DegreesMetaPresent = () => { + const meta51 = document.head.querySelectorAll('meta[http-equiv="Delegate-CH"]'); + if (!meta51.length) { + return false; + } + return Array.from(meta51).some( + meta => !meta.content + ? false + : meta.content.includes('cloud.51degrees') + ); +} + +/** + * Sets the value of a key in the ORTB2 object if the value is not empty + * + * @param {Object} obj The object to set the key in + * @param {string} key The key to set + * @param {any} value The value to set + */ +export const deepSetNotEmptyValue = (obj, key, value) => { + if (!key) { + throw new Error(LOG_PREFIX + ' Key is required'); + } + + if (value) { + deepSetValue(obj, key, value); + } +} + +/** + * Converts all 51Degrees data to ORTB2 format + * + * @param {Object} data51 Response from 51Degrees API + * @param {Object} [data51.device] Device data + * + * @returns {Object} Enriched ORTB2 object + */ +export const convert51DegreesDataToOrtb2 = (data51) => { + let ortb2Data = {}; + + if (!data51) { + return ortb2Data; + } + + ortb2Data = convert51DegreesDeviceToOrtb2(data51.device); + + // placeholder for the next 51Degrees RTD submodule update + + return ortb2Data; +}; + +/** + * Converts 51Degrees device data to ORTB2 format + * + * @param {Object} device 51Degrees device object + * @param {string} [device.deviceid] Device ID (unique 51Degrees identifier) + * @param {string} [device.devicetype] Device type + * @param {string} [device.hardwarevendor] Hardware vendor + * @param {string} [device.hardwaremodel] Hardware model + * @param {string[]} [device.hardwarename] Hardware name + * @param {string} [device.platformname] Platform name + * @param {string} [device.platformversion] Platform version + * @param {number} [device.screenpixelsheight] Screen height in pixels + * @param {number} [device.screenpixelswidth] Screen width in pixels + * @param {number} [device.screenpixelsphysicalheight] Screen physical height in pixels + * @param {number} [device.screenpixelsphysicalwidth] Screen physical width in pixels + * @param {number} [device.pixelratio] Pixel ratio + * @param {number} [device.screeninchesheight] Screen height in inches + * + * @returns {Object} Enriched ORTB2 object + */ +export const convert51DegreesDeviceToOrtb2 = (device) => { + const ortb2Device = {}; + + if (!device) { + return ortb2Device; + } + + const deviceModel = + device.hardwaremodel || ( + device.hardwarename && device.hardwarename.length + ? device.hardwarename.join(',') + : null + ); + + const devicePhysicalPPI = device.screenpixelsphysicalheight && device.screeninchesheight + ? Math.round(device.screenpixelsphysicalheight / device.screeninchesheight) + : null; + + const devicePPI = device.screenpixelsheight && device.screeninchesheight + ? Math.round(device.screenpixelsheight / device.screeninchesheight) + : null; + + deepSetNotEmptyValue(ortb2Device, 'devicetype', ORTB_DEVICE_TYPE_MAP.get(device.devicetype)); + deepSetNotEmptyValue(ortb2Device, 'make', device.hardwarevendor); + deepSetNotEmptyValue(ortb2Device, 'model', deviceModel); + deepSetNotEmptyValue(ortb2Device, 'os', device.platformname); + deepSetNotEmptyValue(ortb2Device, 'osv', device.platformversion); + deepSetNotEmptyValue(ortb2Device, 'h', device.screenpixelsphysicalheight || device.screenpixelsheight); + deepSetNotEmptyValue(ortb2Device, 'w', device.screenpixelsphysicalwidth || device.screenpixelswidth); + deepSetNotEmptyValue(ortb2Device, 'pxratio', device.pixelratio); + deepSetNotEmptyValue(ortb2Device, 'ppi', devicePhysicalPPI || devicePPI); + deepSetNotEmptyValue(ortb2Device, 'ext.fiftyonedegrees_deviceId', device.deviceid); + + return {device: ortb2Device}; +} + +/** + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} moduleConfig Configuration for 1plusX RTD module + * @param {Object} userConsent + */ +export const getBidRequestData = (reqBidsConfigObj, callback, moduleConfig, userConsent) => { + try { + // Get the required config + const {resourceKey, onPremiseJSUrl} = extractConfig(moduleConfig, reqBidsConfigObj); + logMessage('Resource key: ', resourceKey); + logMessage('On-premise JS URL: ', onPremiseJSUrl); + + // Check if 51Degrees meta is present (cloud only) + if (resourceKey) { + logMessage('Checking if 51Degrees meta is present in the document head'); + if (!is51DegreesMetaPresent()) { + logWarn('Delegate-CH meta tag is not present in the document head'); + } + } + + getHighEntropyValues(['model', 'platform', 'platformVersion', 'fullVersionList']).then((hev) => { + // Get 51Degrees JS URL, which is either cloud or on-premise + const scriptURL = get51DegreesJSURL({resourceKey, onPremiseJSUrl, hev}); + logMessage('URL of the script to be injected: ', scriptURL); + + // Inject 51Degrees script, get device data and merge it into the ORTB2 object + loadExternalScript(scriptURL, MODULE_TYPE_RTD, MODULE_NAME, () => { + logMessage('Successfully injected 51Degrees script'); + const fod = /** @type {Object} */ (window.fod); + // Convert and merge device data in the callback + fod.complete((data) => { + logMessage('51Degrees raw data: ', data); + mergeDeep( + reqBidsConfigObj.ortb2Fragments.global, + convert51DegreesDataToOrtb2(data), + ); + logMessage('reqBidsConfigObj: ', reqBidsConfigObj); + callback(); + }); + }, document, {crossOrigin: 'anonymous'}); + }); + } catch (error) { + // In case of an error, log it and continue + logError(error); + callback(); + } +} + +/** + * Init + * @param {Object} config Module configuration + * @param {boolean} userConsent User consent + * @returns true + */ +const init = (config, userConsent) => { + return true; +} + +// 51Degrees RTD submodule object to be registered +export const fiftyOneDegreesSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData, +} + +submodule('realTimeData', fiftyOneDegreesSubmodule); diff --git a/modules/51DegreesRtdProvider.md b/modules/51DegreesRtdProvider.md new file mode 100644 index 00000000000..76fa73803c9 --- /dev/null +++ b/modules/51DegreesRtdProvider.md @@ -0,0 +1,162 @@ +# 51Degrees RTD Submodule + +## Overview + + Module Name: 51Degrees RTD Provider + Module Type: RTD Provider + Maintainer: support@51degrees.com + +## Description + +The 51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). + +The 51Degrees module sets the following fields of the device object: `devicetype`, `make`, `model`, `os`, `osv`, `h`, `w`, `ppi`, `pxratio`. Interested bidder adapters may use these fields as needed. In addition, the module sets `device.ext.fiftyonedegrees_deviceId` to a permanent device ID, which can be rapidly looked up in on-premise data, exposing over 250 properties, including device age, chipset, codec support, price, operating system and app/browser versions, age, and embedded features. + +The module supports on-premise and cloud device detection services, with free options for both. + +A free resource key for use with 51Degrees cloud service can be obtained from [51Degrees cloud configuration](https://configure.51degrees.com/HNZ75HT1). This is the simplest approach to trial the module. + +An interface-compatible self-hosted service can be used with .NET, Java, Node, PHP, and Python. See [51Degrees examples](https://51degrees.com/documentation/_examples__device_detection__getting_started__web__on_premise.html). + +Free cloud and on-premise solutions can be expanded to support unlimited requests, additional properties, and automatic daily on-premise data updates via a [subscription](https://51degrees.com/pricing). + +## Usage + +### Integration + +Compile the 51Degrees RTD Module with other modules and adapters into your Prebid.js build: + +``` +gulp build --modules=rtdModule,51DegreesRtdProvider,appnexusBidAdapter,... +``` + +> Note that the 51Degrees RTD module is dependent on the global real-time data module, `rtdModule`. + +### Prerequisites + +#### Resource Key + +In order to use the module, please first obtain a Resource Key using the [Configurator tool](https://configure.51degrees.com/HNZ75HT1) - choose the following properties: + +* DeviceId +* DeviceType +* HardwareVendor +* HardwareName +* HardwareModel +* PlatformName +* PlatformVersion +* ScreenPixelsHeight +* ScreenPixelsWidth +* ScreenPixelsPhysicalHeight +* ScreenPixelsPhysicalWidth +* ScreenInchesHeight +* ScreenInchesWidth +* PixelRatio + +The Cloud API is **free** to integrate and use. To increase limits, please check [51Degrees pricing](https://51degrees.com/pricing). + +#### User Agent Client Hint (UA-CH) Permissions + +Some UA-CH headers are not available to third parties. To allow the 51Degrees cloud service to access these headers for more accurate detection and lower latency, it is highly recommended to set `Permissions-Policy` in one of two ways: + +In the HTML of the publisher's web page where the Prebid.js wrapper is integrated: + +```html + +``` + +Or in the Response Headers of the publisher's web server: + +```http +Permissions-Policy: ch-ua-arch=(self "https://cloud.51degrees.com"), ch-ua-full-version=(self "https://cloud.51degrees.com"), ch-ua-full-version-list=(self "https://cloud.51degrees.com"), ch-ua-model=(self "https://cloud.51degrees.com"), ch-ua-platform=(self "https://cloud.51degrees.com"), ch-ua-platform-version=(self "https://cloud.51degrees.com") + +Accept-CH: sec-ch-ua-arch, sec-ch-ua-full-version, sec-ch-ua-full-version-list, sec-ch-ua-model, sec-ch-ua-platform, sec-ch-ua-platform-version +``` + +See the [51Degrees documentation](https://51degrees.com/documentation/_device_detection__features__u_a_c_h__overview.html) for more information concerning UA-CH and permissions. + +##### Why not use the GetHighEntropyValues API instead? + +Thanks for asking. + +The script this module injects has a fallback to the GetHighEntropyValues API but does not rely on it as a first (or only) choice route. Please see the illustrative cases below. Although it seems easier, the GHEV API is not supported by all browsers (so the decision to call it should be conditional). Also, even in Chrome, this API will likely be subject to the Privacy Budget in the future. + +In summary, we recommend using `Delegate-CH` http-equiv as the preferred method of obtaining the necessary evidence because it is the fastest and most future-proof method. + +##### Illustrative Cases + +* If the device is iPhone/iPad, there is no point in checking for or calling GetHighEntropyValues at the moment because iOS does not support this API. However, this might change in the future. Platforms like iOS require additional techniques to identify the model, which are not covered via a single API call, and change from version to version of the operating system and browser rendering engine. **When used with iOS, 51Degrees resolves the [iPhone/iPad model groups](https://51degrees.com/documentation/4.4/_device_detection__features__apple_device_table.html) using these techniques.** That is one of the benefits the module brings to the Prebid community, as most solutions do not resolve iPhone/iPad model groups. More on Apple Device Detection [here](https://51degrees.com/documentation/4.4/_device_detection__features__apple_detection.html). + +* If the browser is Firefox on Android or Desktop, there is similarly no point in requesting GHEV, as the API is not supported. + +* If the browser is Chrome, the `Delegate-CH`, if enabled by the publisher, would allow the browser to provide the necessary evidence. However, if this is not implemented, then the dynamic script would fall back to GHEV, which is slower. + +### Configuration + +This module is configured as part of the `realTimeData.dataProviders`. We recommend setting `auctionDelay` to at least 250 ms and ensuring `waitForIt` is set to `true` for the `51Degrees` RTD provider. + +```javascript +pbjs.setConfig({ + debug: false, // turn on for testing, remove in production + realTimeData: { + auctionDelay: 250, + dataProviders: [ + { + name: '51Degrees', + waitForIt: true, // should be true, otherwise the auctionDelay will be ignored + params: { + resourceKey: '', + // Get your resource key from https://configure.51degrees.com/HNZ75HT1 + // alternatively, you can use the on-premise version of the 51Degrees service and connect to your chosen endpoint + // onPremiseJSUrl: 'https://localhost/51Degrees.core.js' + }, + }, + ], + }, +}); +``` + +### Parameters + +> Note that `resourceKey` and `onPremiseJSUrl` are mutually exclusive parameters. Use strictly one of them: either a `resourceKey` for cloud integration or `onPremiseJSUrl` for the on-premise self-hosted integration. + +| Name | Type | Description | Default | +|:----------------------|:--------|:---------------------------------------------------------------------------------------------|:-------------------| +| name | String | Real-time data module name | Always '51Degrees' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (mandatory) | `false` | +| params | Object | | | +| params.resourceKey | String | Your 51Degrees Cloud Resource Key | | +| params.onPremiseJSUrl | String | Direct URL to your self-hosted on-premise JS file (e.g. https://localhost/51Degrees.core.js) | | + +> Note: if you use a third-party Prebid.js wrapper, there might be a chance that the UI will force you to input both `resourceKey` and `onPremiseJSUrl`. In this case, you can set a redundant parameter to a string equal to "0", which will be ignored by the module. + +## Example + +> Note: you need to have a valid resource key to run the example.\ +> It should be set in the configuration instead of ``.\ +> It is located in the `integrationExamples/gpt/51DegreesRtdProvider_example.html` file. + +If you want to see an example of how the 51Degrees RTD module works,\ +run the following command: + +`gulp serve --modules=rtdModule,51DegreesRtdProvider,appnexusBidAdapter` + +and then open the following URL in your browser: + +`http://localhost:9999/integrationExamples/gpt/51DegreesRtdProvider_example.html` + +Open the browser console to see the logs. + +## Customer Notices + +When using the 51Degrees cloud service, publishers need to reference the 51Degrees [client services privacy policy](https://51degrees.com/terms/client-services-privacy-policy) in their customer notices. + +## Optimisation + +To reduce latency when loading the 51Degrees cloud service script, it's recommended to preconnect to the 51Degrees domain. This will establish an early connection, allowing the browser to resolve DNS, set up TCP, and perform the TLS handshake ahead of time, speeding up the script download. + +To enable `preconnect`, add the following in the `` of your HTML: + +```html + +``` diff --git a/modules/prebidmanagerAnalyticsAdapter.js b/modules/AsteriobidPbmAnalyticsAdapter.js similarity index 96% rename from modules/prebidmanagerAnalyticsAdapter.js rename to modules/AsteriobidPbmAnalyticsAdapter.js index 858e30068db..7f56f5064b7 100644 --- a/modules/prebidmanagerAnalyticsAdapter.js +++ b/modules/AsteriobidPbmAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, getParameterByName, logError, parseUrl, logInfo } from '../src/utils.js'; +import { deepClone, generateUUID, getParameterByName, hasNonSerializableProperty, logError, parseUrl, logInfo } from '../src/utils.js'; import {ajaxBuilder} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -9,10 +9,10 @@ import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; /** * prebidmanagerAnalyticsAdapter.js - analytics adapter for prebidmanager */ -export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'prebidmanager'}); +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: 'asteriobidpbm'}); const DEFAULT_EVENT_URL = 'https://endpt.prebidmanager.com/endpoint'; const analyticsType = 'endpoint'; -const analyticsName = 'Prebid Manager Analytics'; +const analyticsName = 'Asteriobid PBM Analytics'; let ajax = ajaxBuilder(0); @@ -199,10 +199,10 @@ function trimBidderRequest(bidderRequest) { } function handleEvent(eventType, eventArgs) { - try { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; - } catch (e) { - // keep eventArgs as is + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} } const pmEvent = {}; diff --git a/modules/AsteriobidPbmAnalyticsAdapter.md b/modules/AsteriobidPbmAnalyticsAdapter.md new file mode 100644 index 00000000000..0331a71b17c --- /dev/null +++ b/modules/AsteriobidPbmAnalyticsAdapter.md @@ -0,0 +1,9 @@ +# Overview + +Module Name: Asteriobid PBM Analytics Adapter +Module Type: Analytics Adapter +Maintainer: admin@prebidmanager.com + +# Description + +Analytics adapter for Asteriobid PBM. Contact admin@prebidmanager.com for information. diff --git a/modules/a1MediaRtdProvider.js b/modules/a1MediaRtdProvider.js index 445ed47181d..1fbe88ecfa0 100644 --- a/modules/a1MediaRtdProvider.js +++ b/modules/a1MediaRtdProvider.js @@ -39,7 +39,7 @@ function loadLbScript(tagname) { linkback.l = true; const scriptUrl = `${SCRIPT_URL}/${tagname}`; - loadExternalScript(scriptUrl, MODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME); } } diff --git a/modules/aaxBlockmeterRtdProvider.js b/modules/aaxBlockmeterRtdProvider.js index a3b7b4812a7..0a72e4e36f1 100644 --- a/modules/aaxBlockmeterRtdProvider.js +++ b/modules/aaxBlockmeterRtdProvider.js @@ -1,6 +1,7 @@ import {isEmptyStr, isStr, logError, isFn, logWarn} from '../src/utils.js'; import {submodule} from '../src/hook.js'; import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; export const _config = { MODULE: 'aaxBlockmeter', @@ -28,7 +29,7 @@ function loadBlockmeter(_rtdConfig) { } const scriptUrl = `https://${url}&${params.join('&')}`; - loadExternalScript(scriptUrl, _config.MODULE); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, _config.MODULE); return true; } diff --git a/modules/acuityadsBidAdapter.js b/modules/acuityadsBidAdapter.js index 5b12eb2133b..bd85d2b3cc0 100644 --- a/modules/acuityadsBidAdapter.js +++ b/modules/acuityadsBidAdapter.js @@ -1,216 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'acuityads'; const AD_URL = 'https://prebid.admanmedia.com/pbjs'; const SYNC_URL = 'https://cs.admanmedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - 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 = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/adagioAnalyticsAdapter.js b/modules/adagioAnalyticsAdapter.js index bb5de41d3ce..410accc946a 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -2,13 +2,15 @@ * Analytics Adapter for Adagio */ +import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; +import { deepAccess, logError, logInfo, logWarn, isPlainObject } from '../src/utils.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { EVENTS } from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; -import { BANNER } from '../src/mediaTypes.js'; -import { getWindowTop, getWindowSelf, deepAccess, logInfo, logError } from '../src/utils.js'; import { getGlobal } from '../src/prebidGlobal.js'; +import { subscribeToGamSlotRenderEndedEvent, SlotRenderEndedEvent } from '../libraries/gptUtils/gptUtils.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -19,6 +21,14 @@ const PREBID_VERSION = '$prebid.version$'; const ENDPOINT = 'https://c.4dex.io/pba.gif'; const CURRENCY_USD = 'USD'; const ADAGIO_CODE = 'adagio'; + +export const _internal = { + getAdagioNs: function() { + return _ADAGIO; + }, + gamSlotCallback +}; + const cache = { auctions: {}, getAuction: function(auctionId, adUnitCode) { @@ -44,37 +54,22 @@ const cache = { }, getAdagioAuctionId(auctionId) { return this.auctionIdReferences[auctionId]; - } -}; -const enc = window.encodeURIComponent; - -/** -/* BEGIN ADAGIO.JS CODE - */ + }, -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; + // Map adunitcode with prebid auction ID + auctionByAdunit: {}, + getAuctionIdByAdunit(adUnitPath, adSlotElementId) { + if (cache.auctionByAdunit[adUnitPath]) { + return { auctionId: cache.auctionByAdunit[adUnitPath], adUnitCode: adUnitPath } + } + if (cache.auctionByAdunit[adSlotElementId]) { + return { auctionId: cache.auctionByAdunit[adSlotElementId], adUnitCode: adSlotElementId } } - } catch (error) { - return false; + return { auctionId: null, adUnitCode: null } } }; -function getCurrentWindow() { - return currentWindow; -}; - -let currentWindow; - -const adagioEnqueue = function adagioEnqueue(action, data) { - getCurrentWindow().ADAGIO.queue.push({ action, data, ts: Date.now() }); -}; - -/** - * END ADAGIO.JS CODE - */ +const enc = window.encodeURIComponent; /** * UTILS FUNCTIONS @@ -150,6 +145,10 @@ function getCurrencyData(bid) { * @param {Object} qp */ function sendRequest(qp) { + if (!qp.org_id || !qp.site) { + logInfo('request is missing org_id or site, skipping beacon.'); + return; + } // Removing null values qp = Object.keys(qp).reduce((acc, key) => { if (qp[key] !== null) { @@ -194,37 +193,36 @@ function getTargetedAuctionId(bid) { */ function handlerAuctionInit(event) { - const w = getCurrentWindow(); + const w = getBestWindowForAdagio(); const prebidAuctionId = event.auctionId; - const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode); + + // adUnitCodes come from `event.bidderRequests` to be sure to keep the ad-units that are valid and will be effectively used during the auction. + // This array can be different than `event.adUnitCodes` because of the usage of conditionnal ad-units (see: https://docs.prebid.org/dev-docs/conditional-ad-units.html) + const adUnitCodes = new Set( + event.bidderRequests + .map(br => br.bids.map(bid => bid.adUnitCode)) + .flat() + ); // Check if Adagio is on the bid requests. - // If not, we don't need to track the auction. const adagioBidRequest = event.bidderRequests.find(bidRequest => isAdagio(bidRequest.bidderCode)); - if (!adagioBidRequest) { - logInfo(`Adagio is not on the bid requests for auction '${prebidAuctionId}'`) - return; - } + + const rtdUid = deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.adg_rtd.uid'); + cache.addPrebidAuctionIdRef(prebidAuctionId, rtdUid); cache.auctions[prebidAuctionId] = {}; adUnitCodes.forEach(adUnitCode => { + // event.adUnits are splitted by mediatypes + // having twin ad-unit codes is ok: https://docs.prebid.org/dev-docs/adunit-reference.html#twin-adunit-codes const adUnits = event.adUnits.filter(adUnit => adUnit.code === adUnitCode); - // Get all bidders configures for the ad unit. - const bidders = removeDuplicates( - adUnits.map(adUnit => adUnit.bids.map(bid => ({bidder: bid.bidder, params: bid.params}))).flat(), - bidder => bidder.bidder - ); - - // Check if Adagio is configured for the ad unit. - // If not, we don't need to track the ad unit. - const adagioBidder = bidders.find(bidder => isAdagio(bidder.bidder)); - if (!adagioBidder) { - logInfo(`Adagio is not configured for ad unit '${adUnitCode}'`); - return; - } + // Get all bidders configured for the ad unit. + // AdUnits with the same code can have a different bidder list, aggregate all of them. + const biddersAggregate = adUnits.reduce((bidders, adUnit) => bidders.concat(adUnit.bids.map(bid => bid.bidder)), []) + // remove duplicates + const bidders = [...new Set(biddersAggregate)]; // Get all media types and banner sizes configured for the ad unit. const mediaTypes = adUnits.map(adUnit => adUnit.mediaTypes); @@ -233,52 +231,69 @@ function handlerAuctionInit(event) { mediaTypeKey => mediaTypeKey ).map(mediaType => getMediaTypeAlias(mediaType)).sort(); const bannerSizes = removeDuplicates( - mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER)) + mediaTypes.filter(mediaType => mediaType.hasOwnProperty(BANNER) && mediaType[BANNER].hasOwnProperty('sizes')) .map(mediaType => mediaType[BANNER].sizes.map(size => size.join('x'))) .flat(), bannerSize => bannerSize ).sort(); - // Get all Adagio bids for the ad unit from the bidRequest. - // If no bids, we don't need to track the ad unit. - const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode); - if (deepAccess(adagioAdUnitBids, 'length', 0) <= 0) { - logInfo(`Adagio is not on the bid requests for ad unit '${adUnitCode}' and auction '${prebidAuctionId}'`) - return; + const sortedBidderNames = bidders.sort(); + + const bidSrcMapper = (bidder) => { + // bidderCode in the context of the bidderRequest is the name given to the bidder in the adunit. + // It is not always the "true" bidder code, it can also be its alias + const request = event.bidderRequests.find(br => br.bidderCode === bidder) + return request ? request.bids[0].src : null } - // Get Adagio params from the first bid. - // We assume that all Adagio bids for a same adunit have the same params. - const params = adagioAdUnitBids[0].params; - const adagioAuctionId = params.adagioAuctionId; - cache.addPrebidAuctionIdRef(prebidAuctionId, adagioAuctionId); + const biddersSrc = sortedBidderNames.map(bidSrcMapper).join(','); + const biddersCode = sortedBidderNames.map(bidder => adapterManager.resolveAlias(bidder)).join(','); - // Get all media types requested for Adagio. - const adagioMediaTypes = removeDuplicates( - adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), - mediaTypeKey => mediaTypeKey - ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + // if adagio was involved in the auction we identified it with rtdUid, if not use the prebid auctionId + const auctionId = rtdUid || prebidAuctionId; + + const adgRtdSession = deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.adg_rtd.session', {}); const qp = { + org_id: adagioAdapter.options.organizationId, + site: adagioAdapter.options.site, v: 0, pbjsv: PREBID_VERSION, - org_id: params.organizationId, - site: params.site, - pv_id: params.pageviewId, - auct_id: adagioAuctionId, + pv_id: _internal.getAdagioNs().pageviewId, + auct_id: auctionId, adu_code: adUnitCode, url_dmn: w.location.hostname, - pgtyp: params.pagetype, - plcmt: params.placement, - t_n: params.testName || null, - t_v: params.testVersion || null, mts: mediaTypesKeys.join(','), ban_szs: bannerSizes.join(','), - bdrs: bidders.map(bidder => bidder.bidder).sort().join(','), - adg_mts: adagioMediaTypes.join(',') + bdrs: sortedBidderNames.join(','), + pgtyp: deepAccess(event.bidderRequests[0], 'ortb2.site.ext.data.pagetype', null), + plcmt: deepAccess(adUnits[0], 'ortb2Imp.ext.data.placement', null), + t_n: adgRtdSession.testName || null, + t_v: adgRtdSession.testVersion || null, + s_id: adgRtdSession.id || null, + s_new: adgRtdSession.new || null, + bdrs_src: biddersSrc, + bdrs_code: biddersCode, }; + if (adagioBidRequest && adagioBidRequest.bids) { + const adagioAdUnitBids = adagioBidRequest.bids.filter(bid => bid.adUnitCode === adUnitCode); + if (adagioAdUnitBids.length > 0) { + // Get all media types requested for Adagio. + const adagioMediaTypes = removeDuplicates( + adagioAdUnitBids.map(bid => Object.keys(bid.mediaTypes)).flat(), + mediaTypeKey => mediaTypeKey + ).flat().map(mediaType => getMediaTypeAlias(mediaType)).sort(); + + qp.adg_mts = adagioMediaTypes.join(','); + // for backward compatibility: if we didn't find organizationId & site but we have a bid from adagio we might still find it in params + qp.org_id = qp.org_id || adagioAdUnitBids[0].params.organizationId; + qp.site = qp.site || adagioAdUnitBids[0].params.site; + } + } + cache.auctions[prebidAuctionId][adUnitCode] = qp; + cache.auctionByAdunit[adUnitCode] = prebidAuctionId; sendNewBeacon(prebidAuctionId, adUnitCode); }); }; @@ -324,13 +339,26 @@ function handlerAuctionEnd(event) { return bid ? getCurrencyData(bid).netCpm : null } + const perfNavigation = performance.getEntriesByType('navigation')[0]; + + const auction = cache.getAuction(auctionId, adUnitCode); + const bdrs = auction.bdrs.split(','); + const bdrsTimeout = auction.bdrs_timeout || []; + cache.updateAuction(auctionId, adUnitCode, { bdrs_bid: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidResponseMapper).join(','), - bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(',') + bdrs_cpm: cache.getBiddersFromAuction(auctionId, adUnitCode).map(bidCpmMapper).join(','), + // check timings at the end of the auction to leave time to the browser to update it + dom_i: Math.round(perfNavigation['domInteractive']) || null, + dom_c: Math.round(perfNavigation['domComplete']) || null, + loa_e: Math.round(perfNavigation['loadEventEnd']) || null, + bdrs_timeout: bdrs.map(b => bdrsTimeout.includes(b) ? '1' : '0').join(','), }); + sendNewBeacon(auctionId, adUnitCode); }); } + function handlerBidWon(event) { let auctionId = getTargetedAuctionId(event); @@ -345,6 +373,8 @@ function handlerBidWon(event) { ? cache.getAdagioAuctionId(event.auctionId) : null); + const perfNavigation = performance.getEntriesByType('navigation')[0]; + cache.updateAuction(auctionId, event.adUnitCode, { win_bdr: event.bidder, win_mt: getMediaTypeAlias(event.mediaType), @@ -353,6 +383,11 @@ function handlerBidWon(event) { win_net_cpm: currencyData.netCpm, win_og_cpm: currencyData.orginalCpm, + // check timings at the end of the auction to leave time to the browser to update it + dom_i: Math.round(perfNavigation['domInteractive']) || null, + dom_c: Math.round(perfNavigation['domComplete']) || null, + loa_e: Math.round(perfNavigation['loadEventEnd']) || null, + // cache bid id auct_id_c: adagioAuctionCacheId, }); @@ -373,14 +408,84 @@ function handlerAdRender(event, isSuccess) { sendNewBeacon(auctionId, adUnitCode); }; +function handlerBidTimeout(args) { + args.forEach(event => { + const auction = cache.getAuction(event.auctionId, event.adUnitCode); + if (!auction) { + logWarn(`bid timeout on auction ${event.auctionId}, with adunitCode ${event.adUnitCode}: could not retrieve auction from cache`); + return; + } + + // an array of bidder names is first created + // in AUCTION_END handler, this array is sorted + // and transformed in a comma-separated list. + const bdrsTimeout = auction.bdrs_timeout || []; + bdrsTimeout.push(event.bidder); + auction.bdrs_timeout = bdrsTimeout; + }); +}; + +/** + * handlerPbsAnalytics add to the cache data coming from Adagio PBS AdResponse. + * The data is retrieved from an AnalyticsTag (set by a custom PBS module named `adg-pba`), + * located in the AdResponse at `response.ext.prebid.analytics.tags[].pba`. + */ +function handlerPbsAnalytics(event) { + const pbaByAdUnit = event.atag.find(e => { + return e.module === 'adg-pba' + })?.pba; + + if (!pbaByAdUnit) { + return; + } + + const adUnitCodes = cache.getAllAdUnitCodes(event.auctionId); + + adUnitCodes.forEach(adUnitCode => { + const pba = pbaByAdUnit[adUnitCode] + + if (isPlainObject(pba)) { + cache.updateAuction(event.auctionId, adUnitCode, { + ...addKeyPrefix(pba, 'e_') + }); + } + }) +} + /** * END HANDLERS */ +/** + * @param {SlotRenderEndedEvent} event + * @returns {void} + */ +function gamSlotCallback(event) { + const { auctionId, adUnitCode } = cache.getAuctionIdByAdunit(event.slot.getAdUnitPath(), event.slot.getSlotElementId()); + if (!auctionId) { + const slotName = `${event.slot.getAdUnitPath()} - ${event.slot.getSlotElementId()}`; + logWarn('Could not find configured ad unit matching GAM render of slot: ' + slotName); + return; + } + + cache.updateAuction(auctionId, adUnitCode, { + adsrv: 'gam', + adsrv_empty: event.isEmpty + }); + + // This event can be triggered after AUCTION_END + // To make sure the data is sent, we must send a new beacon version. + const auction = cache.getAuction(auctionId, adUnitCode) + if (auction?.loa_e !== undefined) { + // loa_e = loadEventEnd + // It means the AUCTION_END has already been sent. + sendNewBeacon(auctionId, adUnitCode); + } +} + let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { track: function(event) { const { eventType, args } = event; - try { switch (eventType) { case EVENTS.AUCTION_INIT: @@ -400,6 +505,12 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { case EVENTS.AD_RENDER_FAILED: handlerAdRender(args, eventType === EVENTS.AD_RENDER_SUCCEEDED); break; + case EVENTS.PBS_ANALYTICS: + handlerPbsAnalytics(args); + break; + case EVENTS.BID_TIMEOUT: + handlerBidTimeout(args); + break; } } catch (error) { logError('Error on Adagio Analytics Adapter', error); @@ -407,7 +518,11 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { try { if (typeof args !== 'undefined' && events.indexOf(eventType) !== -1) { - adagioEnqueue('pb-analytics-event', { eventName: eventType, args }); + _internal.getAdagioNs().queue.push({ + action: 'pb-analytics-event', + data: { eventName: eventType, args }, + ts: Date.now() + }); } } catch (error) { logError('Error on Adagio Analytics Adapter - adagio.js', error); @@ -418,15 +533,29 @@ let adagioAdapter = Object.assign(adapter({ emptyUrl, analyticsType }), { adagioAdapter.originEnableAnalytics = adagioAdapter.enableAnalytics; adagioAdapter.enableAnalytics = config => { - const w = (canAccessTopWindow()) ? getWindowTop() : getWindowSelf(); - currentWindow = w; + _internal.getAdagioNs().versions.adagioAnalyticsAdapter = VERSION; - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.versions = w.ADAGIO.versions || {}; - w.ADAGIO.versions.adagioAnalyticsAdapter = VERSION; + let modules = getGlobal().installedModules; + if (modules && (!modules.length || modules.indexOf('adagioRtdProvider') === -1 || modules.indexOf('rtdModule') === -1)) { + logError('Adagio Analytics Adapter requires rtdModule & adagioRtdProvider modules which are not installed. No beacon will be sent'); + return; + } + adagioAdapter.options = config.options || {}; + if (!adagioAdapter.options.organizationId) { + logWarn('Adagio Analytics Adapter: organizationId is required and is missing will try to fallback on params.'); + } else { + adagioAdapter.options.organizationId = adagioAdapter.options.organizationId.toString(); // allows publisher to pass it as a number + } + if (!adagioAdapter.options.site) { + logWarn('Adagio Analytics Adapter: site is required and is missing will try to fallback on params.'); + } else if (typeof adagioAdapter.options.site !== 'string') { + logWarn('Adagio Analytics Adapter: site should be a string will try to fallback on params.'); + adagioAdapter.options.site = undefined; + } adagioAdapter.originEnableAnalytics(config); + + subscribeToGamSlotRenderEndedEvent(gamSlotCallback) } adapterManager.registerAnalyticsAdapter({ diff --git a/modules/adagioAnalyticsAdapter.md b/modules/adagioAnalyticsAdapter.md index 9fc2cb0bb88..916f9ec9c58 100644 --- a/modules/adagioAnalyticsAdapter.md +++ b/modules/adagioAnalyticsAdapter.md @@ -13,5 +13,9 @@ Analytics adapter for Adagio ```js pbjs.enableAnalytics({ provider: 'adagio', + options: { + organizationId: '1000', // Required. Provided by Adagio + site: 'my-website', // Required. Provided by Adagio + } }); ``` diff --git a/modules/adagioBidAdapter.js b/modules/adagioBidAdapter.js index ac2e578de5c..d44c7e249d6 100644 --- a/modules/adagioBidAdapter.js +++ b/modules/adagioBidAdapter.js @@ -1,271 +1,47 @@ -import {find} from '../src/polyfill.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { + canAccessWindowTop, cleanObj, deepAccess, deepClone, generateUUID, getDNT, - getUniqueIdentifierStr, getWindowSelf, - getWindowTop, - inIframe, isArray, isFn, - isInteger, isNumber, - isArrayOfNums, + isStr, logError, logInfo, logWarn, - mergeDeep, - isStr, } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {loadExternalScript} from '../src/adloader.js'; -import {verify} from 'criteo-direct-rsa-validate/build/verify.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {getRefererInfo, parseDomain} from '../src/refererDetection.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; -import {OUTSTREAM} from '../src/video.js'; -import { getGlobal } from '../src/prebidGlobal.js'; +import { getRefererInfo, parseDomain } from '../src/refererDetection.js'; +import { OUTSTREAM, validateOrtbVideoFields } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; +import { _ADAGIO } from '../libraries/adagioUtils/adagioUtils.js'; +import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { find } from '../src/polyfill.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { userSync } from '../src/userSync.js'; -import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'adagio'; const LOG_PREFIX = 'Adagio:'; -const FEATURES_VERSION = '1'; 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; -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const BB_PUBLICATION = 'adagio'; const BB_RENDERER_DEFAULT = 'renderer'; export const BB_RENDERER_URL = `https://${BB_PUBLICATION}.bbvms.com/r/$RENDERER.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'; -// This provide a whitelist and a basic validation of OpenRTB 2.5 options used by the Adagio SSP. -// Accept all options but 'protocol', 'companionad', 'companiontype', 'ext' -// 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) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => isArrayOfNums(value), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), - 'placement': (value) => { - logWarn(LOG_PREFIX, 'The OpenRTB video param `placement` is deprecated and should not be used anymore.'); - return isInteger(value) - }, - 'plcmt': (value) => isInteger(value), - 'linearity': (value) => isInteger(value), - 'skip': (value) => [1, 0].includes(value), - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => isArrayOfNums(value), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => isInteger(value), - 'playbackmethod': (value) => isArrayOfNums(value), - 'playbackend': (value) => isInteger(value), - 'delivery': (value) => isArrayOfNums(value), - 'pos': (value) => isInteger(value), - 'api': (value) => isArrayOfNums(value) -}; - -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 = deepAccess(adagioStorage, 'session.rnd'); - let newSession = false; - - if (internal.isNewSession(adagioStorage)) { - newSession = true; - random = Math.random(); - } - - const data = { - session: { - new: newSession, - rnd: random - } - } - - mergeDeep(exchangeData, adagioStorage, data); - - internal.enqueue({ - action: 'session', - ts: Date.now(), - data: exchangeData - }); - }, - - getExchangeData() { - return exchangeData - } - }; -})(); - -export function adagioScriptFromLocalStorageCb(ls) { - try { - if (!ls) { - logWarn(`${LOG_PREFIX} script not found.`); - return; - } - - const hashRgx = /^(\/\/ hash: (.+)\n)(.+\n)$/; - - if (!hashRgx.test(ls)) { - 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]; - - if (verify(content, hash, ADAGIO_PUBKEY, ADAGIO_PUBKEY_E)) { - logInfo(`${LOG_PREFIX} start script.`); - Function(ls)(); // eslint-disable-line no-new-func - } else { - logWarn(`${LOG_PREFIX} invalid script found.`); - storage.removeDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY); - } - } - } catch (err) { - logError(LOG_PREFIX, err); - } -} - -export function getAdagioScript() { - storage.getDataFromLocalStorage(ADAGIO_LOCALSTORAGE_KEY, (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) { - logInfo(`${LOG_PREFIX} unable to clear Adagio scripts from localstorage.`); - } - } - }); -} - -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - -function getCurrentWindow() { - return currentWindow || getWindowSelf(); -} - -function isSafeFrameWindow() { - const ws = getWindowSelf(); - return !!(ws.$sf && ws.$sf.ext); -} - -function initAdagio() { - if (canAccessTopWindow()) { - currentWindow = (canAccessTopWindow()) ? getWindowTop() : 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.pbjs = '$prebid.version$'; - w.ADAGIO.isSafeFrameWindow = isSafeFrameWindow(); - - storage.getDataFromLocalStorage('adagio', (storageData) => { - try { - GlobalExchange.prepareExchangeData(storageData); - } catch (e) { - logError(LOG_PREFIX, e); - } - }); - - getAdagioScript(); -} - -function enqueue(ob) { - const w = internal.getCurrentWindow(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; - w.ADAGIO.queue.push(ob); -}; - -function getPageviewId() { - const w = internal.getCurrentWindow(); - - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pageviewId = w.ADAGIO.pageviewId || generateUUID(); - - return w.ADAGIO.pageviewId; -}; - +/** + * Returns the window.ADAGIO global object used to store Adagio data. + * This object is created in window.top if possible, otherwise in window.self. + */ function getDevice() { const language = navigator.language ? 'language' : 'userLanguage'; return { @@ -287,30 +63,6 @@ function getSite(bidderRequest) { }; }; -function getElementFromTopWindow(element, currentWindow) { - try { - if (getWindowTop() === currentWindow) { - if (!element.getAttribute('id')) { - element.setAttribute('id', `adg-${getUniqueIdentifierStr()}`); - } - return element; - } else { - 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) { - logWarn(`${LOG_PREFIX}`, err); - return false; - } -}; - function autoDetectAdUnitElementIdFromGpt(adUnitCode) { const autoDetectedAdUnit = getGptSlotInfoForAdUnitCode(adUnitCode); @@ -335,42 +87,28 @@ function isRendererPreferredFromPublisher(bidRequest) { } /** - * - * @param {object} adagioStorage - * @returns {boolean} + * Check if the publisher has defined its own video player and uses it for all ad-units. + * If not or if the `backupOnly` flag is true, this means we use our own player (BlueBillywig) defined in this adapter. */ -function isNewSession(adagioStorage) { - const now = Date.now(); - const { lastActivityTime, vwSmplg } = deepAccess(adagioStorage, 'session', {}); - return ( - !isNumber(lastActivityTime) || - !isNumber(vwSmplg) || - (now - lastActivityTime) > MAX_SESS_DURATION - ) +function getPlayerName(bidRequest) { + return _internal.isRendererPreferredFromPublisher(bidRequest) ? 'other' : 'adagio'; ; } -function setPlayerName(bidRequest) { - const playerName = (internal.isRendererPreferredFromPublisher(bidRequest)) ? 'other' : 'adagio'; - - if (playerName === 'other') { - logWarn(`${LOG_PREFIX} renderer.backupOnly has not been set. Adagio recommends to use its own player to get expected behavior.`); - } - - return playerName; -} +function hasRtd() { + const rtdConfigs = config.getConfig('realTimeData.dataProviders') || []; + return rtdConfigs.find(provider => provider.name === 'adagio'); +}; -export const internal = { - enqueue, - getPageviewId, +export const _internal = { + canAccessWindowTop, + getAdagioNs: function() { + return _ADAGIO; + }, getDevice, getSite, - getElementFromTopWindow, getRefererInfo, - adagioScriptFromLocalStorageCb, - getCurrentWindow, - canAccessTopWindow, + hasRtd, isRendererPreferredFromPublisher, - isNewSession }; function _getGdprConsent(bidderRequest) { @@ -403,17 +141,6 @@ function _getUspConsent(bidderRequest) { return (deepAccess(bidderRequest, 'uspConsent')) ? { uspConsent: bidderRequest.uspConsent } : false; } -function _getGppConsent(bidderRequest) { - let gpp = deepAccess(bidderRequest, 'gppConsent.gppString') - let gppSid = deepAccess(bidderRequest, 'gppConsent.applicableSections') - - if (!gpp || !gppSid) { - gpp = deepAccess(bidderRequest, 'ortb2.regs.gpp', '') - gppSid = deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []) - } - return { gpp, gppSid } -} - function _getSchain(bidRequest) { return deepAccess(bidRequest, 'schain'); } @@ -424,6 +151,12 @@ function _getEids(bidRequest) { } } +/** + * Merge and compute video params set at mediaTypes and bidder params level + * + * @param {object} bidRequest - copy of the original bidRequest object. + * @returns {void} + */ function _buildVideoBidRequest(bidRequest) { const videoAdUnitParams = deepAccess(bidRequest, 'mediaTypes.video', {}); const videoBidderParams = deepAccess(bidRequest, 'params.video', {}); @@ -444,22 +177,11 @@ function _buildVideoBidRequest(bidRequest) { }; if (videoParams.context && videoParams.context === OUTSTREAM) { - bidRequest.mediaTypes.video.playerName = setPlayerName(bidRequest); + videoParams.playerName = getPlayerName(bidRequest); } - // 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]; - logWarn(`${LOG_PREFIX} The OpenRTB video param ${paramName} has been skipped due to misformating. Please refer to OpenRTB 2.5 spec.`); - } - } - }); + bidRequest.mediaTypes.video = videoParams; + validateOrtbVideoFields(bidRequest); } function _parseNativeBidResponse(bid) { @@ -591,7 +313,7 @@ function _getFloors(bidRequest) { floors.push(cleanObj({ mt: mediaType, s: isArray(size) ? `${size[0]}x${size[1]}` : undefined, - f: (!isNaN(info.floor) && info.currency === CURRENCY) ? info.floor : undefined + f: (!isNaN(info?.floor) && info?.currency === CURRENCY) ? info?.floor : undefined })); } @@ -664,13 +386,14 @@ function autoFillParams(bid) { 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) { + // `useAdUnitCodeAsPlacement` is an edge case. Useful when a Prebid Manager cannot handle properly params setting. + // In Prebid.js 9, `placement` should be defined in ortb2Imp and the `useAdUnitCodeAsPlacement` param should be removed + bid.params.placement = deepAccess(bid, 'ortb2Imp.ext.data.placement', bid.params.placement); + if (!bid.params.placement && (adgGlobalConf.useAdUnitCodeAsPlacement === true || bid.params.useAdUnitCodeAsPlacement === true)) { bid.params.placement = bid.adUnitCode; } - bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.elementId', null) || bid.params.adUnitElementId; - + bid.params.adUnitElementId = deepAccess(bid, 'ortb2Imp.ext.data.divId', bid.params.adUnitElementId); if (!bid.params.adUnitElementId) { if (adgGlobalConf.useAdUnitCodeAsAdUnitElementId === true || bid.params.useAdUnitCodeAsAdUnitElementId === true) { bid.params.adUnitElementId = bid.adUnitCode; @@ -684,209 +407,6 @@ function autoFillParams(bid) { setExtraParam(bid, 'category'); } -function getPageDimensions() { - if (isSafeFrameWindow() || !canAccessTopWindow()) { - return ''; - } - - // the page dimension can be computed on window.top only. - const wt = getWindowTop(); - const body = wt.document.querySelector('body'); - - if (!body) { - return ''; - } - const html = wt.document.documentElement; - const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); - const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); - - return `${pageWidth}x${pageHeight}`; -} - -/** - * @todo Move to prebid Core as Utils. - * @returns - */ -function getViewPortDimensions() { - if (!isSafeFrameWindow() && !canAccessTopWindow()) { - return ''; - } - - const viewportDims = { w: 0, h: 0 }; - - if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); - return ''; - } - - const sfGeom = ws.$sf.ext.geom(); - - if (!sfGeom || !sfGeom.win) { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().win` property'); - return ''; - } - - viewportDims.w = Math.round(sfGeom.w); - viewportDims.h = Math.round(sfGeom.h); - } else { - // window.top based computing - const wt = getWindowTop(); - viewportDims.w = wt.innerWidth; - viewportDims.h = wt.innerHeight; - } - - return `${viewportDims.w}x${viewportDims.h}`; -} - -function getSlotPosition(adUnitElementId) { - if (!adUnitElementId) { - return ''; - } - - if (!isSafeFrameWindow() && !canAccessTopWindow()) { - return ''; - } - - const position = { x: 0, y: 0 }; - - if (isSafeFrameWindow()) { - const ws = getWindowSelf(); - - if (typeof ws.$sf.ext.geom !== 'function') { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api.'); - return ''; - } - - const sfGeom = ws.$sf.ext.geom(); - - if (!sfGeom || !sfGeom.self) { - logWarn(LOG_PREFIX, 'Unable to compute from safeframe api. Missing `geom().self` property'); - return ''; - } - - position.x = Math.round(sfGeom.t); - position.y = Math.round(sfGeom.l); - } else if (canAccessTopWindow()) { - try { - // window.top based computing - const wt = getWindowTop(); - const d = wt.document; - - let domElement; - - if (inIframe() === true) { - const ws = getWindowSelf(); - const currentElement = ws.document.getElementById(adUnitElementId); - domElement = internal.getElementFromTopWindow(currentElement, ws); - } else { - domElement = wt.document.getElementById(adUnitElementId); - } - - if (!domElement) { - return ''; - } - - let box = domElement.getBoundingClientRect(); - - const docEl = d.documentElement; - const body = d.body; - const clientTop = d.clientTop || body.clientTop || 0; - const clientLeft = d.clientLeft || body.clientLeft || 0; - const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; - const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; - - const elComputedStyle = wt.getComputedStyle(domElement, null); - const mustDisplayElement = elComputedStyle.display === 'none'; - - if (mustDisplayElement) { - logWarn(LOG_PREFIX, 'The element is hidden. The slot position cannot be computed.'); - } - - position.x = Math.round(box.left + scrollLeft - clientLeft); - position.y = Math.round(box.top + scrollTop - clientTop); - } catch (err) { - logError(LOG_PREFIX, err); - return ''; - } - } 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.bidderRequestsCount || 1; -} - -/** - * domLoading feature is computed on window.top if reachable. - */ -function getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - -function storeRequestInAdagioNS(bidRequest) { - const w = getCurrentWindow(); - // Store adUnits config. - // If an adUnitCode has already been stored, it will be replaced. - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.pbjsAdUnits = w.ADAGIO.pbjsAdUnits.filter((adUnit) => adUnit.code !== bidRequest.adUnitCode); - - let printNumber - if (bidRequest.features && bidRequest.features.print_number) { - printNumber = bidRequest.features.print_number; - } else if (bidRequest.params.features && bidRequest.params.features.print_number) { - printNumber = bidRequest.params.features.print_number; - } - - w.ADAGIO.pbjsAdUnits.push({ - code: bidRequest.adUnitCode, - mediaTypes: bidRequest.mediaTypes || {}, - sizes: (bidRequest.mediaTypes && bidRequest.mediaTypes.banner && Array.isArray(bidRequest.mediaTypes.banner.sizes)) ? bidRequest.mediaTypes.banner.sizes : bidRequest.sizes, - bids: [{ - bidder: bidRequest.bidder, - params: bidRequest.params // use the updated bid.params object with auto-detected params - }], - auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter - pageviewId: internal.getPageviewId(), - printNumber, - localPbjs: '$$PREBID_GLOBAL$$', - localPbjsRef: getGlobal() - }); - - // (legacy) Store internal adUnit information - w.ADAGIO.adUnits[bidRequest.adUnitCode] = { - auctionId: bidRequest.auctionId, // this auctionId has been generated by adagioBidAdapter - pageviewId: internal.getPageviewId(), - printNumber, - }; -} - // See https://support.bluebillywig.com/developers/vast-renderer/ const OUTSTREAM_RENDERER = { bootstrapPlayer: function(bid) { @@ -971,7 +491,6 @@ export const spec = { // Note: `bid.params.placement` is not related to the video param `placement`. if (!(bid.params.organizationId && bid.params.site && bid.params.placement)) { logWarn(`${LOG_PREFIX} at least one required param is missing.`); - // internal.enqueue(debugData()); return false; } @@ -983,22 +502,30 @@ export const spec = { validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); const secure = (location.protocol === 'https:') ? 1 : 0; - const device = internal.getDevice(); - const site = internal.getSite(bidderRequest); - const pageviewId = internal.getPageviewId(); + const device = _internal.getDevice(); + const site = _internal.getSite(bidderRequest); + const pageviewId = _internal.getAdagioNs().pageviewId; const gdprConsent = _getGdprConsent(bidderRequest) || {}; const uspConsent = _getUspConsent(bidderRequest) || {}; const coppa = _getCoppa(); - const gppConsent = _getGppConsent(bidderRequest) + const { gpp, gpp_sid: gppSid } = deepAccess(bidderRequest, 'ortb2.regs', {}); const schain = _getSchain(validBidRequests[0]); const eids = _getEids(validBidRequests[0]) || []; const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled') - const usIfr = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') + const canSyncWithIframe = syncEnabled && userSync.canBidderRegisterSync('iframe', 'adagio') // We don't validate the dsa object in adapter and let our server do it. const dsa = deepAccess(bidderRequest, 'ortb2.regs.ext.dsa'); - const aucId = generateUUID() + // If no session data is provided, we always generate a new one. + const sessionData = deepAccess(bidderRequest, 'ortb2.site.ext.data.adg_rtd.session', {}); + if (!Object.keys(sessionData).length) { + logInfo(LOG_PREFIX, 'No session data provided. A new session is be generated.') + sessionData.new = true; + sessionData.rnd = Math.random() + } + + const aucId = deepAccess(bidderRequest, 'ortb2.site.ext.data.adg_rtd.uid') || generateUUID() const adUnits = validBidRequests.map(rawBidRequest => { const bidRequest = deepClone(rawBidRequest); @@ -1006,13 +533,6 @@ export const spec = { // Fix https://github.com/prebid/Prebid.js/issues/9781 bidRequest.auctionId = aucId - const globalFeatures = GlobalExchange.getOrSetGlobalFeatures(); - const features = { - ...globalFeatures, - print_number: getPrintNumber(bidRequest.adUnitCode, bidderRequest).toString(), - adunit_position: getSlotPosition(bidRequest.params.adUnitElementId) // adUnitElementId à déplacer ??? - }; - // Force the Split Keyword to be a String if (bidRequest.params.splitKeyword) { if (isStr(bidRequest.params.splitKeyword) || isNumber(bidRequest.params.splitKeyword)) { @@ -1056,24 +576,6 @@ export const spec = { } } - 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 // We need to use `rawBidRequest` as param because: // - adagioBidAdapter generates its own auctionId due to transmitTid activity limitation (see https://github.com/prebid/Prebid.js/pull/10079) @@ -1127,8 +629,14 @@ export const spec = { bidRequest.gpid = gpid; } - // store the whole bidRequest (adUnit) object in the ADAGIO namespace. - storeRequestInAdagioNS(bidRequest); + // features are added by the adagioRtdProvider. + const rawFeatures = { + ...deepAccess(bidRequest, 'ortb2.site.ext.data.adg_rtd.features', {}), + print_number: (bidRequest.bidderRequestsCount || 1).toString(), + adunit_position: deepAccess(bidRequest, 'ortb2Imp.ext.data.adg_rtd.adunit_position', null) + } + // Clean the features object from null or undefined values. + bidRequest.features = Object.entries(rawFeatures).reduce((a, [k, v]) => (v == null ? a : (a[k] = v, a)), {}) // Remove some params that are not needed on the server side. delete bidRequest.params.siteId; @@ -1165,7 +673,6 @@ export const spec = { // Those params are not sent to the server. // They are used for further operations on analytics adapter. validBidRequests.forEach(rawBidRequest => { - rawBidRequest.params.adagioAuctionId = aucId rawBidRequest.params.pageviewId = pageviewId }); @@ -1176,18 +683,21 @@ export const spec = { url: ENDPOINT, data: { organizationId: organizationId, + hasRtd: _internal.hasRtd() ? 1 : 0, secure: secure, device: device, site: site, pageviewId: pageviewId, adUnits: groupedAdUnits[organizationId], - data: GlobalExchange.getExchangeData(), + data: { + session: sessionData + }, regs: { gdpr: gdprConsent, coppa: coppa, ccpa: uspConsent, - gpp: gppConsent.gpp, - gppSid: gppConsent.gppSid, + gpp: gpp || '', + gppSid: gppSid || [], dsa: dsa // populated if exists }, schain: schain, @@ -1195,9 +705,7 @@ export const spec = { eids: eids }, prebidVersion: '$prebid.version$', - featuresVersion: FEATURES_VERSION, - usIfr: usIfr, - adgjs: storage.localStorageIsEnabled() + usIfr: canSyncWithIframe }, options: { contentType: 'text/plain' @@ -1214,11 +722,13 @@ export const spec = { const response = serverResponse.body; if (response) { if (response.data) { - internal.enqueue({ - action: 'ssp-data', - ts: Date.now(), - data: response.data - }); + if (_internal.hasRtd()) { + _internal.getAdagioNs().queue.push({ + action: 'ssp-data', + ts: Date.now(), + data: response.data + }); + } } if (response.bids) { response.bids.forEach(bidObj => { @@ -1282,6 +792,4 @@ export const spec = { }, }; -initAdagio(); - registerBidder(spec); diff --git a/modules/adagioRtdProvider.js b/modules/adagioRtdProvider.js new file mode 100644 index 00000000000..2260ae8d31a --- /dev/null +++ b/modules/adagioRtdProvider.js @@ -0,0 +1,701 @@ +/** + * This module adds the adagio provider to the Real Time Data module (rtdModule). + * The {@link module:modules/realTimeData} module is required. + * @module modules/adagioRtdProvider + * @requires module:modules/realTimeData + */ +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import adapterManager from '../src/adapterManager.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + canAccessWindowTop, + deepAccess, + deepSetValue, + generateUUID, + getDomLoadingDuration, + getSafeframeGeometry, + getUniqueIdentifierStr, + getWindowSelf, + getWindowTop, + inIframe, + isNumber, + isSafeFrameWindow, + isStr, + prefixLog +} from '../src/utils.js'; +import { _ADAGIO, getBestWindowForAdagio } from '../libraries/adagioUtils/adagioUtils.js'; +import { getGptSlotInfoForAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').adUnit} adUnit + */ +const SUBMODULE_NAME = 'adagio'; +const ADAGIO_BIDDER_CODE = 'adagio'; +const GVLID = 617; +const SCRIPT_URL = 'https://script.4dex.io/a/latest/adagio.js'; +const SESS_DURATION = 30 * 60 * 1000; +export const PLACEMENT_SOURCES = { + ORTB: 'ortb', // implicit default, not used atm. + ADUNITCODE: 'code', + GPID: 'gpid' +}; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); + +const { logError, logWarn } = prefixLog('AdagioRtdProvider:'); + +// Guard to avoid storing the same bid data several times. +const guard = new Set(); + +/** + * Store the sampling data. + * This data is used to determine if beacons should be sent to adagio. + * The sampling data + */ +const _SESSION = (function() { + /** + * @type {SessionData} + */ + const data = { + session: {} + }; + + return { + init: () => { + // helper function to determine if the session is new. + const isNewSession = (lastActivity) => { + const now = Date.now(); + return (!isNumber(lastActivity) || (now - lastActivity) > SESS_DURATION); + }; + + storage.getDataFromLocalStorage('adagio', (storageValue) => { + // session can be an empty object + const { rnd, new: isNew = false, vwSmplg, vwSmplgNxt, lastActivityTime, id, testName, testVersion, initiator, pages } = _internal.getSessionFromLocalStorage(storageValue); + + // isNew can be `true` if the session has been initialized by the A/B test snippet (external) + const isNewSess = (initiator === 'snippet') ? isNew : isNewSession(lastActivityTime); + + data.session = { + rnd, + pages: pages || 1, + new: isNewSess, // legacy: `new` was used but the choosen name is not good. + // Don't use values if they are not defined. + ...(vwSmplg !== undefined && { vwSmplg }), + ...(vwSmplgNxt !== undefined && { vwSmplgNxt }), + ...(lastActivityTime !== undefined && { lastActivityTime }), + ...(id !== undefined && { id }), + ...(testName !== undefined && { testName }), + ...(testVersion !== undefined && { testVersion }), + ...(initiator !== undefined && { initiator }), + }; + + // `initiator` is a pseudo flag used to know if the session has been initialized by the A/B test snippet (external). + // If the AB Test snippet has not been used, then `initiator` value is `adgjs` or `undefined`. + // The check on `testName` is used to ensure that the A/B test values are removed. + if (initiator !== 'snippet' && (isNewSess || testName)) { + data.session.new = true; + data.session.id = generateUUID(); + data.session.rnd = Math.random(); + // Ensure that the A/B test values are removed. + delete data.session.testName; + delete data.session.testVersion; + } + + _internal.getAdagioNs().queue.push({ + action: 'session', + ts: Date.now(), + data: { + session: { + ...data.session + } + } + }); + }); + }, + get: function() { + return data.session; + } + }; +})(); + +const _FEATURES = (function() { + /** + * @type {Features} + */ + const features = { + initialized: false, + data: {}, + }; + + return { + // reset is used for testing purpose + reset: function() { + features.initialized = false; + features.data = {}; + }, + get: function() { + const w = getBestWindowForAdagio(); + + if (!features.initialized) { + features.data = { + page_dimensions: getPageDimensions().toString(), + viewport_dimensions: getViewPortDimensions().toString(), + user_timestamp: getTimestampUTC().toString(), + dom_loading: getDomLoadingDuration(w).toString(), + }; + features.initialized = true; + } + + return { ...features.data }; + } + }; +})(); + +export const _internal = { + getAdagioNs: function() { + return _ADAGIO; + }, + + getSession: function() { + return _SESSION; + }, + + getFeatures: function() { + return _FEATURES; + }, + + getGuard: function() { + return guard; + }, + + /** + * Ensure that the bidder is Adagio. + * + * @param {string} alias + * @returns {boolean} + */ + isAdagioBidder: function (alias) { + if (!alias) { + return false; + } + return (alias + adapterManager.aliasRegistry[alias]).toLowerCase().includes(ADAGIO_BIDDER_CODE); + }, + + /** + * Returns the session data from the localStorage. + * + * @param {string} storageValue - The value stored in the localStorage. + * @returns {Session} + */ + getSessionFromLocalStorage: function(storageValue) { + const _default = { + new: true, + rnd: Math.random() + }; + + const obj = JSON.parse(storageValue, function(name, value) { + if (name.charAt(0) !== '_' || name === '') { + return value; + } + }); + + return (!obj || !obj.session) ? _default : obj.session; + } +}; + +function loadAdagioScript(config) { + storage.localStorageIsEnabled(isValid => { + if (!isValid) { + return; + } + + loadExternalScript(SCRIPT_URL, MODULE_TYPE_RTD, SUBMODULE_NAME, undefined, undefined, { + id: `adagiojs-${getUniqueIdentifierStr()}`, + 'data-pid': config.params.organizationId + }); + }); +} + +/** + * Initialize the Adagio RTD Module. + * @param {Object} config + * @param {Object} _userConsent + * @returns {boolean} + */ +function init(config, _userConsent) { + if (!isStr(config.params?.organizationId) || !isStr(config.params?.site)) { + logError('organizationId is required and must be a string.'); + return false; + } + + _internal.getAdagioNs().hasRtd = true; + + _internal.getSession().init(); + + registerEventsForAdServers(config); + + loadAdagioScript(config); + + return true; +} + +/** + * onBidRequest is called for each bidder during an auction and contains the bids for that bidder. + * + * @param {*} bidderRequest + * @param {*} config + * @param {*} _userConsent + */ +function onBidRequest(bidderRequest, config, _userConsent) { + // setTimeout trick to ensure that the `bidderRequest.params` values updated by a bidder adapter are taken into account. + // @todo: Check why we have to do it like this, and if there is a better way. Check how the event is dispatched in rtdModule/index.js + setTimeout(() => { + bidderRequest.bids.forEach(bid => { + const uid = deepAccess(bid, 'ortb2.site.ext.data.adg_rtd.uid'); + if (!uid) { + logError('The `uid` is required to store the request in the ADAGIO namespace.'); + return; + } + + // No need to store the same info several times. + // `uid` is unique as it is generated by the RTD module itself for each auction. + const key = `${bid.adUnitCode}-${uid}`; + if (_internal.getGuard().has(key)) { + return; + } + + _internal.getGuard().add(key); + storeRequestInAdagioNS(bid, config); + }); + }, 1); +} + +/** + * onGetBidRequestData is called once per auction. + * Update both the `ortb2Fragments` and `ortb2Imp` objects with features computed for Adagio. + * + * @param {*} bidReqConfig + * @param {*} callback + * @param {*} config + */ +function onGetBidRequestData(bidReqConfig, callback, config) { + const configParams = deepAccess(config, 'params', {}); + const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const features = _internal.getFeatures().get(); + const ext = { + uid: generateUUID(), + pageviewId: _ADAGIO.pageviewId, + features: { ...features }, + session: { ..._SESSION.get() } + }; + + deepSetValue(ortb2Site, `ext.data.adg_rtd`, ext); + + const adUnits = bidReqConfig.adUnits || getGlobal().adUnits || []; + adUnits.forEach(adUnit => { + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + const ortb2Imp = deepAccess(adUnit, 'ortb2Imp'); + + // A divId is required to compute the slot position and later to track viewability. + // If nothing has been explicitly set, we try to get the divId from the GPT slot and fallback to the adUnit code in last resort. + let divId = deepAccess(ortb2Imp, 'ext.data.divId') + if (!divId) { + divId = getGptSlotInfoForAdUnitCode(adUnit.code).divId; + deepSetValue(ortb2Imp, `ext.data.divId`, divId || adUnit.code); + } + + const slotPosition = getSlotPosition(divId); + deepSetValue(ortb2Imp, `ext.data.adg_rtd.adunit_position`, slotPosition); + + // It is expected that the publisher set a `adUnits[].ortb2Imp.ext.data.placement` value. + // Btw, We allow fallback sources to programmatically set this value. + // The source is defined in the `config.params.placementSource` and the possible values are `code` or `gpid`. + // (Please note that this `placement` is not related to the oRTB video property.) + if (!deepAccess(ortb2Imp, 'ext.data.placement')) { + const { placementSource = '' } = configParams; + + switch (placementSource.toLowerCase()) { + case PLACEMENT_SOURCES.ADUNITCODE: + deepSetValue(ortb2Imp, 'ext.data.placement', adUnit.code); + break; + case PLACEMENT_SOURCES.GPID: + deepSetValue(ortb2Imp, 'ext.data.placement', deepAccess(ortb2Imp, 'ext.gpid')); + break; + default: + logWarn('`ortb2Imp.ext.data.placement` is missing and `params.definePlacement` is not set in the config.'); + } + } + + // We expect that `pagetype`, `category`, `placement` are defined in FPD `ortb2.site.ext.data` and `adUnits[].ortb2Imp.ext.data` objects. + // Btw, we have to ensure compatibility with publishers that use the "legacy" adagio params at the adUnit.params level. + const adagioBid = adUnit.bids.find(bid => _internal.isAdagioBidder(bid.bidder)); + if (adagioBid) { + // ortb2 level + let mustWarnOrtb2 = false; + if (!deepAccess(ortb2Site, 'ext.data.pagetype') && adagioBid.params.pagetype) { + deepSetValue(ortb2Site, 'ext.data.pagetype', adagioBid.params.pagetype); + mustWarnOrtb2 = true; + } + if (!deepAccess(ortb2Site, 'ext.data.category') && adagioBid.params.category) { + deepSetValue(ortb2Site, 'ext.data.category', adagioBid.params.category); + mustWarnOrtb2 = true; + } + + // ortb2Imp level + let mustWarnOrtb2Imp = false; + if (!deepAccess(ortb2Imp, 'ext.data.placement')) { + if (adagioBid.params.placement) { + deepSetValue(ortb2Imp, 'ext.data.placement', adagioBid.params.placement); + mustWarnOrtb2Imp = true; + } + } + + if (mustWarnOrtb2) { + logWarn('`pagetype` and `category` must be defined in the FPD `ortb2.site.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + } + if (mustWarnOrtb2Imp) { + logWarn('`placement` must be defined in the FPD `adUnits[].ortb2Imp.ext.data` object. Relying on `adUnits[].bids.adagio.params` is deprecated.'); + } + } + }); + + callback(); +} + +export const adagioRtdSubmodule = { + name: SUBMODULE_NAME, + gvlid: GVLID, + init: init, + getBidRequestData: onGetBidRequestData, + onBidRequestEvent: onBidRequest, +}; + +submodule('realTimeData', adagioRtdSubmodule); + +// --- +// +// internal functions moved from adagioBidAdapter.js to adagioRtdProvider.js. +// +// Several of these functions could be redistribued in Prebid.js core or in a library +// +// --- + +/** + * storeRequestInAdagioNS store ad-units in the ADAGIO namespace for further usage. + * Not all the properties are stored, only the ones that are useful for adagio.js. + * + * @param {*} bid - The bid object. Correspond to the bidRequest.bids[i] object. + * @param {*} config - The RTD module configuration. + * @returns {void} + */ +function storeRequestInAdagioNS(bid, config) { + try { + const { bidder, adUnitCode, mediaTypes, params, auctionId, bidderRequestsCount, ortb2, ortb2Imp } = bid; + + const { organizationId, site } = config.params; + + const ortb2Data = deepAccess(ortb2, 'site.ext.data', {}); + const ortb2ImpData = deepAccess(ortb2Imp, 'ext.data', {}); + + // TODO: `bidderRequestsCount` must be incremented with s2s context, actually works only for `client` context + // see: https://github.com/prebid/Prebid.js/pull/11295/files#diff-d5c9b255c545e5097d1cd2f49e7dad309b731e34d788f9c28432ad43ebcd7785L114 + const data = { + bidder, + adUnitCode, + mediaTypes, + params, + auctionId, + bidderRequestsCount, + ortb2: ortb2Data, + ortb2Imp: ortb2ImpData, + localPbjs: '$$PREBID_GLOBAL$$', + localPbjsRef: getGlobal(), + organizationId, + site + }; + + _internal.getAdagioNs().queue.push({ + action: 'store', + ts: Date.now(), + data + }); + } catch (e) { + logError(e); + } +} + +function getElementFromTopWindow(element, currentWindow) { + try { + if (getWindowTop() === currentWindow) { + if (!element.getAttribute('id')) { + element.setAttribute('id', `adg-${getUniqueIdentifierStr()}`); + } + return element; + } else { + 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) { + logWarn(err); + return false; + } +}; + +function getSlotPosition(divId) { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { + return ''; + } + + const position = { x: 0, y: 0 }; + + if (isSafeFrameWindow()) { + const { self } = getSafeframeGeometry() || {}; + + if (!self) { + return ''; + } + + position.x = Math.round(self.t); + position.y = Math.round(self.l); + } else { + try { + // window.top based computing + const wt = getWindowTop(); + const d = wt.document; + + let domElement; + + if (inIframe() === true) { + const ws = getWindowSelf(); + const currentElement = ws.document.getElementById(divId); + domElement = getElementFromTopWindow(currentElement, ws); + } else { + domElement = wt.document.getElementById(divId); + } + + if (!domElement) { + return ''; + } + + let box = domElement.getBoundingClientRect(); + + const docEl = d.documentElement; + const body = d.body; + const clientTop = d.clientTop || body.clientTop || 0; + const clientLeft = d.clientLeft || body.clientLeft || 0; + const scrollTop = wt.pageYOffset || docEl.scrollTop || body.scrollTop; + const scrollLeft = wt.pageXOffset || docEl.scrollLeft || body.scrollLeft; + + const elComputedStyle = wt.getComputedStyle(domElement, null); + const mustDisplayElement = elComputedStyle.display === 'none'; + + if (mustDisplayElement) { + logWarn('The element is hidden. The slot position cannot be computed.'); + } + + position.x = Math.round(box.left + scrollLeft - clientLeft); + position.y = Math.round(box.top + scrollTop - clientTop); + } catch (err) { + logError(err); + return ''; + } + } + + return `${position.x}x${position.y}`; +} + +function getPageDimensions() { + if (isSafeFrameWindow() || !canAccessWindowTop()) { + return ''; + } + + // the page dimension can be computed on window.top only. + const wt = getWindowTop(); + const body = wt.document.querySelector('body'); + + if (!body) { + return ''; + } + const html = wt.document.documentElement; + const pageWidth = Math.max(body.scrollWidth, body.offsetWidth, html.clientWidth, html.scrollWidth, html.offsetWidth); + const pageHeight = Math.max(body.scrollHeight, body.offsetHeight, html.clientHeight, html.scrollHeight, html.offsetHeight); + + return `${pageWidth}x${pageHeight}`; +} + +function getViewPortDimensions() { + if (!isSafeFrameWindow() && !canAccessWindowTop()) { + return ''; + } + + const viewportDims = { w: 0, h: 0 }; + + if (isSafeFrameWindow()) { + const { win } = getSafeframeGeometry() || {}; + + if (!win) { + return ''; + } + + viewportDims.w = Math.round(win.w); + viewportDims.h = Math.round(win.h); + } else { + // window.top based computing + const wt = getWindowTop(); + viewportDims.w = wt.innerWidth; + viewportDims.h = wt.innerHeight; + } + + return `${viewportDims.w}x${viewportDims.h}`; +} + +function getTimestampUTC() { + // timestamp returned in seconds + return Math.floor(new Date().getTime() / 1000) - new Date().getTimezoneOffset() * 60; +} + +/** + * registerEventsForAdServers bind adagio listeners to ad-server events. + * Theses events are used to track the viewability and attention. + * + * @param {*} config + * @returns {void} + */ +function registerEventsForAdServers(config) { + const GPT_EVENTS = new Set([ + 'impressionViewable', + 'slotRenderEnded', + 'slotVisibilityChanged', + ]); + + const SAS_EVENTS = new Set([ + 'noad', + 'setHeaderBiddingWinner', + ]); + + const AST_EVENTS = new Set([ + 'adLoaded', + ]); + + // Listen to ad-server events in current window + // as we can be safe in a Post-Bid scenario. + const ws = getWindowSelf(); + + // Keep a reference to the window on which the listener is attached. + // this is used to avoid to bind event several times. + if (!Array.isArray(_internal.getAdagioNs().windows)) { + _internal.getAdagioNs().windows = []; + } + + let selfStoredWindow = _internal.getAdagioNs().windows.find(_w => _w.self === ws); + if (!selfStoredWindow) { + selfStoredWindow = { self: ws }; + _internal.getAdagioNs().windows.push(selfStoredWindow); + } + + const register = (namespace, command, selfWindow, adserver, cb) => { + try { + if (selfWindow.adserver === adserver) { + return; + } + ws[namespace] = ws[namespace] || {}; + ws[namespace][command] = ws[namespace][command] || []; + cb(); + } catch (e) { + logError(e); + } + }; + + register('googletag', 'cmd', ws, 'gpt', () => { + ws.googletag.cmd.push(() => { + GPT_EVENTS.forEach(eventName => { + ws.googletag.pubads().addEventListener(eventName, (args) => { + _internal.getAdagioNs().queue.push({ + action: 'gpt-event', + data: { eventName, args, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'gpt'; + }); + }); + + register('sas', 'cmd', ws, 'sas', () => { + ws.sas.cmd.push(() => { + SAS_EVENTS.forEach(eventName => { + ws.sas.events.on(eventName, (args) => { + _internal.getAdagioNs().queue.push({ + action: 'sas-event', + data: { eventName, args, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'sas'; + }); + }); + + // https://learn.microsoft.com/en-us/xandr/seller-tag/on-event + register('apntag', 'anq', ws, 'ast', () => { + ws.apntag.anq.push(() => { + AST_EVENTS.forEach(eventName => { + ws.apntag.onEvent(eventName, () => { + _internal.getAdagioNs().queue.push({ + action: 'ast-event', + data: { eventName, args: arguments, _window: ws }, + ts: Date.now(), + }); + }); + }); + selfStoredWindow.adserver = 'ast'; + }); + }); +}; + +// --- end of internal functions ----- // + +/** + * @typedef {Object} AdagioWindow + * @property {Window} self + * @property {string} adserver - 'gpt', 'sas', 'ast' + */ + +/** + * @typedef {Object} AdagioGlobal + * @property {Object} adUnits + * @property {Array} pbjsAdUnits + * @property {Array} queue + * @property {Array} windows + */ + +/** + * @typedef {Object} Session + * @property {boolean} new - True if the session is new. + * @property {number} rnd - Random number used to determine if the session is new. + * @property {number} vwSmplg - View sampling rate. + * @property {number} vwSmplgNxt - Next view sampling rate. + * @property {number} lastActivityTime - Last activity time. + * @property {number} pages - current number of pages seen. + */ + +/** + * @typedef {Object} SessionData + * @property {Session} session - the session data. + */ + +/** + * @typedef {Object} Features + * @property {boolean} initialized - True if the features are initialized. + * @property {Object} data - the features data. + */ diff --git a/modules/adagioRtdProvider.md b/modules/adagioRtdProvider.md new file mode 100644 index 00000000000..a51137d571f --- /dev/null +++ b/modules/adagioRtdProvider.md @@ -0,0 +1,38 @@ +# Overview + +Module Name: Adagio Rtd Provider +Module Type: Rtd Provider +Maintainer: dev@adagio.io + +# Description + +This module is exclusively used in combination with Adagio Bidder Adapter (SSP) and/or with Adagio prebid server endpoint, and mandatory for Adagio customers. +It computes and collects data required to leverage Adagio viewability and attention prediction engine. + +Features are computed for the Adagio bidder only and placed into `ortb2.ext` and `AdUnit.ortb2Imp.ext.data`. + +To collect data, an external script is loaded by the provider. +It relies on the listening of ad-server events. +Supported ad-servers are GAM, Smart Ad Server, Xandr. Custom ad-server can also be used, +please contact [contact@adagio.io](contact@adagio.io) for more information. + +# Integration + +```bash +gulp build --modules=adagioBidAdapter,rtdModule,adagioRtdProvider +``` + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders:[{ + name: 'adagio', + params: { + organizationId: '1000' // Required. Provided by Adagio + site: 'my-site' // Required. Provided by Adagio + placementSource: 'ortb' // Optional. Where to find the "placement" value. Possible values: 'ortb' | 'code' | 'gpid' + } + }] + } +}); +``` diff --git a/modules/adbookpspBidAdapter.js b/modules/adbookpspBidAdapter.js deleted file mode 100644 index cb03f2ffc17..00000000000 --- a/modules/adbookpspBidAdapter.js +++ /dev/null @@ -1,830 +0,0 @@ -import {find, includes} from '../src/polyfill.js'; -import {config} from '../src/config.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {getStorageManager} from '../src/storageManager.js'; -import { - deepAccess, - deepSetValue, - flatten, - generateUUID, - inIframe, - isArray, - isEmptyStr, - isNumber, - isPlainObject, - isStr, - logError, - logWarn, - triggerPixel, - uniques -} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.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) { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - 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: bidderRequest.refererInfo.domain, - page: bidderRequest.refererInfo.page, - ref: bidderRequest.refererInfo.ref, - }, - source: buildSource(validBidRequests, bidderRequest), - device: buildDevice(), - regs: buildRegs(bidderRequest), - user: buildUser(bidderRequest), - imp: validBidRequests.map(buildImp), - ext: { - adbook: { - config: getBidderConfig(), - version: { - prebid: '$prebid.version$', - adapter: VERSION, - }, - }, - }, - }; - - return JSON.stringify(request); -} - -function buildDevice() { - const { innerWidth, innerHeight } = common.getWindowDimensions(); - - const device = { - w: innerWidth, - h: innerHeight, - js: true, - ua: navigator.userAgent, - dnt: - navigator.doNotTrack === 'yes' || - navigator.doNotTrack == '1' || - navigator.msDoNotTrack == '1' - ? 1 - : 0, - }; - - const deviceConfig = common.getConfig('device'); - - if (isPlainObject(deviceConfig)) { - return { ...device, ...deviceConfig }; - } - - return device; -} - -function buildRegs(bidderRequest) { - const regs = { - coppa: common.getConfig('coppa') === true ? 1 : 0, - }; - - if (bidderRequest.gdprConsent) { - deepSetValue( - regs, - 'ext.gdpr', - bidderRequest.gdprConsent.gdprApplies ? 1 : 0 - ); - deepSetValue( - regs, - 'ext.gdprConsentString', - bidderRequest.gdprConsent.consentString || '' - ); - } - - if (bidderRequest.uspConsent) { - deepSetValue(regs, 'ext.us_privacy', bidderRequest.uspConsent); - } - - return regs; -} - -function buildSource(bidRequests, bidderRequest) { - const source = { - fd: 1, - tid: bidderRequest.ortb2.source.tid, - }; - const schain = deepAccess(bidRequests, '0.schain'); - - if (schain) { - deepSetValue(source, 'ext.schain', schain); - } - - return source; -} - -function buildUser(bidderRequest) { - const user = { - id: getUserId(), - }; - - if (bidderRequest.gdprConsent) { - user.gdprConsentString = bidderRequest.gdprConsent.consentString || ''; - } - - return user; -} - -function buildImp(bidRequest) { - let impBase = { - id: bidRequest.bidId, - tagid: bidRequest.adUnitCode, - ext: buildImpExt(bidRequest), - }; - - return Object.keys(bidRequest.mediaTypes) - .filter((mediaType) => includes(SUPPORTED_MEDIA_TYPES, mediaType)) - .reduce((imp, mediaType) => { - return { - ...imp, - [mediaType]: buildMediaTypeObject(mediaType, bidRequest), - }; - }, impBase); -} - -function buildMediaTypeObject(mediaType, bidRequest) { - switch (mediaType) { - case BANNER: - return buildBannerObject(bidRequest); - case VIDEO: - return buildVideoObject(bidRequest); - default: - logWarn(`${BIDDER_CODE}: Unsupported media type ${mediaType}!`); - } -} - -function buildBannerObject(bidRequest) { - const format = bidRequest.mediaTypes.banner.sizes.map((size) => { - const [w, h] = size; - - return { w, h }; - }); - const { w, h } = format[0]; - - return { - pos: 0, - topframe: inIframe() ? 0 : 1, - format, - w, - h, - }; -} - -function buildVideoObject(bidRequest) { - const { w, h } = getVideoSize(bidRequest); - let videoObj = { - w, - h, - }; - - for (const param of VIDEO_PARAMS) { - const paramsValue = deepAccess(bidRequest, `params.video.${param}`); - const mediaTypeValue = deepAccess( - bidRequest, - `mediaTypes.video.${param}` - ); - - if (paramsValue || mediaTypeValue) { - videoObj[param] = paramsValue || mediaTypeValue; - } - } - - return videoObj; -} - -function getVideoSize(bidRequest) { - const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize', [[]]); - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - if (isNumber(w) && isNumber(h)) { - return { w, h }; - } - - return { - w: playerSize[0][0], - h: playerSize[0][1], - } -} - -function buildImpExt(validBidRequest) { - const defaultOrgId = getBidderConfig('orgId'); - const { orgId, placementId } = validBidRequest.params || {}; - const effectiverOrgId = orgId || defaultOrgId; - const ext = {}; - - if (placementId) { - deepSetValue(ext, 'adbook.placementId', placementId); - } - - if (effectiverOrgId) { - deepSetValue(ext, 'adbook.orgId', effectiverOrgId); - } - - return ext; -} - -/** - * BID RESPONSE - */ - -function interpretResponse(bidResponse, bidderRequest) { - const bidderRequestBody = safeJSONparse(bidderRequest.data); - - if ( - deepAccess(bidderRequestBody, 'id') != - deepAccess(bidResponse, 'body.id') - ) { - logError( - `${BIDDER_CODE}: Bid response id does not match bidder request id` - ); - - return []; - } - - const referrer = deepAccess(bidderRequestBody, 'site.ref', ''); - const incomingBids = deepAccess(bidResponse, 'body.seatbid', []) - .filter((seat) => isArray(seat.bid)) - .reduce((bids, seat) => bids.concat(seat.bid), []) - .filter(validateBid(bidderRequestBody)); - const targetingMap = buildTargetingMap(incomingBids); - - return impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponse.body.cur, - referrer, - targetingMap - ); -} - -function impBidsToPrebidBids( - incomingBids, - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap -) { - return incomingBids - .map( - impToPrebidBid( - bidderRequestBody, - bidResponseCurrency, - referrer, - targetingMap - ) - ) - .filter((i) => i !== null); -} - -const impToPrebidBid = - (bidderRequestBody, bidResponseCurrency, referrer, targetingMap) => (bid, bidIndex) => { - try { - const bidRequest = findBidRequest(bidderRequestBody, bid); - - if (!bidRequest) { - logError(`${BIDDER_CODE}: Could not match bid to bid request`); - - return null; - } - const categories = deepAccess(bid, 'cat', []); - const mediaType = getMediaType(bid.adm); - let prebidBid = { - ad: bid.adm, - adId: bid.adid, - adserverTargeting: targetingMap[bidIndex], - adUnitCode: bidRequest.tagid, - bidderRequestId: bidderRequestBody.id, - bidId: bid.id, - cpm: bid.price, - creativeId: bid.crid || bid.id, - currency: bidResponseCurrency || getBidderConfig('defaultCurrency'), - height: bid.h, - lineItemId: deepAccess(bid, 'ext.liid'), - mediaType, - meta: { - advertiserDomains: bid.adomain, - mediaType, - primaryCatId: categories[0], - secondaryCatIds: categories.slice(1), - }, - netRevenue: true, - nurl: bid.nurl, - referrer: referrer, - requestId: bid.impid, - ttl: getBidderConfig('bidTTL'), - width: bid.w, - }; - - if (mediaType === VIDEO) { - prebidBid = { - ...prebidBid, - ...getVideoSpecificParams(bidRequest, bid), - }; - } - - if (deepAccess(bid, 'ext.pa_win') === true) { - prebidBid.auctionWinner = true; - } - return prebidBid; - } catch (error) { - logError(`${BIDDER_CODE}: Error while building bid`, error); - - return null; - } - }; - -function getVideoSpecificParams(bidRequest, bid) { - return { - height: bid.h || bidRequest.video.h, - vastXml: bid.adm, - width: bid.w || bidRequest.video.w, - }; -} - -function buildTargetingMap(bids) { - const impIds = bids.map(({ impid }) => impid).filter(uniques); - const values = impIds.reduce((result, id) => { - result[id] = { - lineItemIds: [], - orderIds: [], - dealIds: [], - adIds: [], - adAndOrderIndexes: [] - }; - - return result; - }, {}); - - bids.forEach((bid, bidIndex) => { - let impId = bid.impid; - values[impId].lineItemIds.push(bid.ext.liid); - values[impId].dealIds.push(bid.dealid); - values[impId].adIds.push(bid.adid); - - if (deepAccess(bid, 'ext.ordid')) { - values[impId].orderIds.push(bid.ext.ordid); - bid.ext.ordid.split(TARGETING_VALUE_SEPARATOR).forEach((ordid, ordIndex) => { - let adIdIndex = values[impId].adIds.indexOf(bid.adid); - values[impId].adAndOrderIndexes.push(adIdIndex + '_' + ordIndex) - }) - } - }); - - const targetingMap = {}; - - bids.forEach((bid, bidIndex) => { - let id = bid.impid; - - targetingMap[bidIndex] = { - hb_liid_adbookpsp: values[id].lineItemIds.join(TARGETING_VALUE_SEPARATOR), - hb_deal_adbookpsp: values[id].dealIds.join(TARGETING_VALUE_SEPARATOR), - hb_ad_ord_adbookpsp: values[id].adAndOrderIndexes.join(TARGETING_VALUE_SEPARATOR), - hb_adid_c_adbookpsp: values[id].adIds.join(TARGETING_VALUE_SEPARATOR), - hb_ordid_adbookpsp: values[id].orderIds.join(TARGETING_VALUE_SEPARATOR), - }; - }) - return targetingMap; -} - -/** - * VALIDATION - */ - -function hasRequiredParams(bidRequest) { - const value = - deepAccess(bidRequest, 'params.placementId') != null || - deepAccess(bidRequest, 'params.orgId') != null || - getBidderConfig('orgId') != null; - - if (!value) { - logError(`${BIDDER_CODE}: missing orgId and placementId parameter`); - } - - return value; -} - -function isValidBannerRequest(bidRequest) { - const value = validateSizes( - deepAccess(bidRequest, 'mediaTypes.banner.sizes', []) - ); - - return value; -} - -function isValidVideoRequest(bidRequest) { - const value = - isArray(deepAccess(bidRequest, 'mediaTypes.video.mimes')) && - validateVideoSizes(bidRequest); - - return value; -} - -function validateSize(size) { - return isArray(size) && size.length === 2 && size.every(isNumber); -} - -function validateSizes(sizes) { - return isArray(sizes) && sizes.length > 0 && sizes.every(validateSize); -} - -function validateVideoSizes(bidRequest) { - const { w, h } = deepAccess(bidRequest, 'mediaTypes.video', {}); - - return ( - validateSizes( - deepAccess(bidRequest, 'mediaTypes.video.playerSize') - ) || - (isNumber(w) && isNumber(h)) - ); -} - -function validateBid(bidderRequestBody) { - return function (bid) { - const mediaType = getMediaType(bid.adm); - const bidRequest = findBidRequest(bidderRequestBody, bid); - let validators = commonBidValidators; - - if (mediaType === BANNER) { - validators = [...commonBidValidators, ...bannerBidValidators]; - } - - const value = validators.every((validator) => validator(bid, bidRequest)); - - if (!value) { - logWarn(`${BIDDER_CODE}: Invalid bid`, bid); - } - - return value; - }; -} - -const commonBidValidators = [ - (bid) => isPlainObject(bid), - (bid) => isNonEmptyStr(bid.adid), - (bid) => isNonEmptyStr(bid.adm), - (bid) => isNonEmptyStr(bid.id), - (bid) => isNonEmptyStr(bid.impid), - (bid) => isNonEmptyStr(deepAccess(bid, 'ext.liid')), - (bid) => isNumber(bid.price), -]; - -const bannerBidValidators = [ - validateBannerDimension('w'), - validateBannerDimension('h'), -]; - -function validateBannerDimension(dimension) { - return function (bid, bidRequest) { - if (bid[dimension] == null) { - return bannerHasSingleSize(bidRequest); - } - - return isNumber(bid[dimension]); - }; -} - -function bannerHasSingleSize(bidRequest) { - return deepAccess(bidRequest, 'banner.format', []).length === 1; -} - -/** - * USER SYNC - */ - -export const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent) { - return responses - .map((response) => deepAccess(response, 'body.ext.sync')) - .filter(isArray) - .reduce(flatten, []) - .filter(validateSync(syncOptions)) - .map(applyConsents(gdprConsent, uspConsent)); -} - -const validateSync = (syncOptions) => (sync) => { - return ( - ((sync.type === 'image' && syncOptions.pixelEnabled) || - (sync.type === 'iframe' && syncOptions.iframeEnabled)) && - sync.url - ); -}; - -const applyConsents = (gdprConsent, uspConsent) => (sync) => { - const url = getUrlBuilder(sync.url); - - if (gdprConsent) { - url.set('gdpr', gdprConsent.gdprApplies ? 1 : 0); - url.set('consentString', gdprConsent.consentString || ''); - } - if (uspConsent) { - url.set('us_privacy', encodeURIComponent(uspConsent)); - } - if (common.getConfig('coppa') === true) { - url.set('coppa', 1); - } - - return { ...sync, url: url.toString() }; -}; - -function getUserId() { - const id = getUserIdFromStorage() || common.generateUUID(); - - setUserId(id); - - return id; -} - -function getUserIdFromStorage() { - const id = storage.localStorageIsEnabled() - ? storage.getDataFromLocalStorage(USER_ID_KEY) - : storage.getCookie(USER_ID_KEY); - - if (!validateUUID(id)) { - return; - } - - return id; -} - -function setUserId(userId) { - if (storage.localStorageIsEnabled()) { - storage.setDataInLocalStorage(USER_ID_KEY, userId); - } - - if (storage.cookiesAreEnabled()) { - const expires = new Date(Date.now() + USER_ID_COOKIE_EXP).toISOString(); - - storage.setCookie(USER_ID_KEY, userId, expires); - } -} - -function validateUUID(uuid) { - return /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test( - uuid - ); -} - -/** - * EVENT TRACKING - */ - -function onBidWon(bid) { - if (!getBidderConfig('winTrackingEnabled')) { - return; - } - - const wurl = buildWinUrl(bid); - - if (wurl !== null) { - triggerPixel(wurl); - } - - if (isStr(bid.nurl)) { - triggerPixel(bid.nurl); - } -} - -function buildWinUrl(bid) { - try { - const url = getUrlBuilder(getBidderConfig('winTrackingUrl')); - - url.set('impId', bid.requestId); - url.set('reqId', bid.bidderRequestId); - url.set('bidId', bid.bidId); - - return url.toString(); - } catch (_) { - logError( - `${BIDDER_CODE}: Could not build win tracking URL with %s`, - getBidderConfig('winTrackingUrl') - ); - - return null; - } -} - -/** - * COMMON - */ - -const VAST_REGEXP = /VAST\s+version/; - -function getMediaType(adm) { - const videoRegex = new RegExp(VAST_REGEXP); - - if (videoRegex.test(adm)) { - return VIDEO; - } - - const markup = safeJSONparse(adm.replace(/\\/g, '')); - - if (markup && isPlainObject(markup.native)) { - return NATIVE; - } - - return BANNER; -} - -function safeJSONparse(...args) { - try { - return JSON.parse(...args); - } catch (_) { - return undefined; - } -} - -function isNonEmptyStr(value) { - return isStr(value) && !isEmptyStr(value); -} - -function findBidRequest(bidderRequest, bid) { - return find(bidderRequest.imp, (imp) => imp.id === bid.impid); -} - -function getBidderConfig(property) { - if (!property) { - return common.getConfig(`${BIDDER_CODE}`); - } - - return common.getConfig(`${BIDDER_CODE}.${property}`); -} - -const getUrlBase = function (url) { - return url.split('?')[0]; -}; - -const getUrlQuery = function (url) { - const query = url.split('?')[1]; - - if (!query) { - return; - } - - return '?' + query.split('#')[0]; -}; - -const getUrlHash = function (url) { - const hash = url.split('#')[1]; - - if (!hash) { - return; - } - - return '#' + hash; -}; - -const getUrlBuilder = function (url) { - const hash = getUrlHash(url); - const base = getUrlBase(url); - const query = getUrlQuery(url); - const pairs = []; - - function set(key, value) { - pairs.push([key, value]); - - return { - set, - toString, - }; - } - - function toString() { - if (!pairs.length) { - return url; - } - - const queryString = pairs - .map(function (pair) { - return pair.join('='); - }) - .join('&'); - - if (!query) { - return base + '?' + queryString + (hash || ''); - } - - return base + query + '&' + queryString + (hash || ''); - } - - return { - set, - toString, - }; -}; - -export const common = { - generateUUID: function () { - return generateUUID(); - }, - getConfig: function (property) { - return config.getConfig(property); - }, - getWindowDimensions: function () { - return { - innerWidth: window.innerWidth, - innerHeight: window.innerHeight, - }; - }, -}; diff --git a/modules/adbookpspBidAdapter.md b/modules/adbookpspBidAdapter.md deleted file mode 100644 index e258b1fd7c3..00000000000 --- a/modules/adbookpspBidAdapter.md +++ /dev/null @@ -1,191 +0,0 @@ -### 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/addefendBidAdapter.js b/modules/addefendBidAdapter.js index dbb186fdc86..a646400b083 100644 --- a/modules/addefendBidAdapter.js +++ b/modules/addefendBidAdapter.js @@ -1,5 +1,4 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const BIDDER_CODE = 'addefend'; @@ -17,7 +16,7 @@ export const spec = { }, buildRequests: function(validBidRequests, bidderRequest) { let bid = { - v: getGlobal().version, + v: 'v' + '$prebid.version$', auctionId: false, pageId: false, gdpr_applies: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? bidderRequest.gdprConsent.gdprApplies : 'true', diff --git a/modules/adfBidAdapter.js b/modules/adfBidAdapter.js index 881b1adfcc4..b74c3efc628 100644 --- a/modules/adfBidAdapter.js +++ b/modules/adfBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {deepAccess, deepClone, deepSetValue, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, mergeDeep, parseSizesInput, setOnAny} from '../src/utils.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; @@ -75,9 +75,10 @@ export const spec = { mediaType: '*' }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; + const bidfloor = floorInfo?.floor; + const bidfloorcur = floorInfo?.currency; const { mid, inv, mname } = bid.params; + const impExtData = bid.ortb2Imp?.ext?.data; const imp = { id: id + 1, @@ -85,6 +86,7 @@ export const spec = { bidfloor, bidfloorcur, ext: { + data: impExtData, bidder: { inv, mname @@ -253,15 +255,6 @@ export const spec = { registerBidder(spec); -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - function flatten(arr) { return [].concat(...arr); } diff --git a/modules/adgridBidAdapter.js b/modules/adgridBidAdapter.js new file mode 100644 index 00000000000..71c55a54395 --- /dev/null +++ b/modules/adgridBidAdapter.js @@ -0,0 +1,189 @@ +import { _each, isEmpty, deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; + +const BIDDER = Object.freeze({ + CODE: 'adgrid', + HOST: 'https://api-prebid.adgrid.io', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/auction', + SUPPORTED_MEDIA_TYPES: [BANNER, VIDEO], +}); + +const CURRENCY = Object.freeze({ + KEY: 'currency', + US_DOLLAR: 'USD', +}); + +function isBidRequestValid(bid) { + if (!bid || !bid.params) { + return false; + } + + return !!bid.params.domainId; +} + +/** + * Return some extra params + */ +function getAudience(validBidRequests, bidderRequest) { + const params = { + domain: deepAccess(bidderRequest, 'refererInfo.page') + }; + + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies')) { + params.gdpr = 1; + params.gdprConsent = deepAccess(bidderRequest, 'gdprConsent.consentString'); + } + + if (deepAccess(bidderRequest, 'uspConsent')) { + params.usp = deepAccess(bidderRequest, 'uspConsent'); + } + + if (deepAccess(validBidRequests[0], 'schain')) { + params.schain = deepAccess(validBidRequests[0], 'schain'); + } + + if (deepAccess(validBidRequests[0], 'userId')) { + params.userIds = deepAccess(validBidRequests[0], 'userId'); + } + + if (deepAccess(validBidRequests[0], 'userIdAsEids')) { + params.userEids = deepAccess(validBidRequests[0], 'userIdAsEids'); + } + + if (bidderRequest.gppConsent) { + params.gpp = bidderRequest.gppConsent.gppString; + params.gppSid = bidderRequest.gppConsent.applicableSections?.toString(); + } else if (bidderRequest.ortb2?.regs?.gpp) { + params.gpp = bidderRequest.ortb2.regs.gpp; + params.gppSid = bidderRequest.ortb2.regs.gpp_sid; + } + + return params; +} + +function buildRequests(validBidRequests, bidderRequest) { + const currencyObj = config.getConfig(CURRENCY.KEY); + const currency = (currencyObj && currencyObj.adServerCurrency) ? currencyObj.adServerCurrency : 'USD'; + const bids = []; + + _each(validBidRequests, bid => { + bids.push(getBidData(bid)) + }); + + const bidsParams = Object.assign({}, { + url: window.location.href, + timeout: bidderRequest.timeout, + ts: new Date().getTime(), + device: { + size: [ + window.screen.width, + window.screen.height + ] + }, + bids + }); + + // Add currency if not USD + if (currency != null && currency != CURRENCY.US_DOLLAR) { + bidsParams.cur = currency; + } + + bidsParams.audience = getAudience(validBidRequests, bidderRequest); + + // Passing geo location data if found in prebid config + bidsParams.geodata = config.getConfig('adgGeodata') || {}; + + return Object.assign({}, bidderRequest, { + method: BIDDER.REQUEST_METHOD, + url: `${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, + data: bidsParams, + currency: currency, + options: { + withCredentials: false, + contentType: 'application/json' + } + }); +} + +function interpretResponse(response, bidRequest) { + let bids = response.body; + const bidResponses = []; + + if (isEmpty(bids)) { + return bidResponses; + } + + if (typeof bids !== 'object') { + return bidResponses; + } + + bids = bids.bids; + + bids.forEach((adUnit) => { + const bidResponse = { + requestId: adUnit.bidId, + cpm: Number(adUnit.cpm), + width: adUnit.width, + height: adUnit.height, + ttl: 300, + creativeId: adUnit.creativeId, + netRevenue: true, + currency: adUnit.currency || bidRequest.currency, + mediaType: adUnit.mediaType + }; + + if (adUnit.mediaType == 'video') { + if (adUnit.admUrl) { + bidResponse.vastUrl = adUnit.admUrl; + } else { + bidResponse.vastXml = adUnit.adm; + } + } else { + bidResponse.ad = adUnit.ad; + } + + bidResponses.push(bidResponse); + }); + + return bidResponses; +} + +function getBidData(bid) { + const bidData = { + requestId: bid.bidId, + tid: bid.ortb2Imp?.ext?.tid, + deviceW: bid.ortb2?.device?.w, + deviceH: bid.ortb2?.device?.h, + deviceUa: bid.ortb2?.device?.ua, + domain: bid.ortb2?.site?.publisher?.domain, + domainId: bid.params.domainId, + code: bid.adUnitCode + }; + + if (bid.mediaTypes != null) { + if (bid.mediaTypes.banner != null) { + bidData.mediaType = 'banner'; + bidData.sizes = bid.mediaTypes.banner.sizes; + } else if (bid.mediaTypes.video != null) { + bidData.mediaType = 'video'; + bidData.sizes = bid.mediaTypes.video.playerSize; + bidData.videoData = bid.mediaTypes.video; + bidData.videoParams = bid.params.video; + } + } + + return bidData; +} + +export const spec = { + code: BIDDER.CODE, + isBidRequestValid, + buildRequests, + interpretResponse, + supportedMediaTypes: BIDDER.SUPPORTED_MEDIA_TYPES +}; + +registerBidder(spec); diff --git a/modules/adgridBidAdapter.md b/modules/adgridBidAdapter.md new file mode 100644 index 00000000000..94eda01e895 --- /dev/null +++ b/modules/adgridBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +**Module Name**: AdGrid Bidder Adapter +**Module Type**: Bidder Adapter +**Maintainer**: support@adgrid.io + +# Description + +The AdGrid Bidding Adapter requires setup and approval before beginning. Please reach out to for more details. + +# Test Parameters + +```javascript +var adUnits = [ + // Banner adUnit + { + code: 'test-div-1', + mediaTypes:{ + banner:{ + sizes: [[300, 250]] + } + } + bids: [{ + bidder: 'adgrid', + params: { + domainId: 12345 + } + }] + }, + { + code: 'test-div-2', + mediaTypes:{ + banner:{ + sizes: [[728, 90], [320, 50]] + } + } + bids: [{ + bidder: 'adgrid', + params: { + domainId: 67890 + } + }] + } +]; +``` diff --git a/modules/adhashBidAdapter.js b/modules/adhashBidAdapter.js index 96e93883de6..7eb91dfcd52 100644 --- a/modules/adhashBidAdapter.js +++ b/modules/adhashBidAdapter.js @@ -7,6 +7,7 @@ const VERSION = '3.6'; const BAD_WORD_STEP = 0.1; const BAD_WORD_MIN = 0.2; const ADHASH_BIDDER_CODE = 'adhash'; +const storage = getStorageManager({ bidderCode: ADHASH_BIDDER_CODE }); /** * Function that checks the page where the ads are being served for brand safety. @@ -120,7 +121,7 @@ function brandSafety(badWords, maxScore) { .replaceAll(/\s\s+/g, ' ') .toLowerCase() .trim(); - const content = window.top.document.body.innerText.toLowerCase(); + const content = window.top.document.body.textContent.toLowerCase(); // \p{L} matches a single unicode code point in the category 'letter'. Matches any kind of letter from any language. const regexp = new RegExp('[\\p{L}]+', 'gu'); const wordsMatched = content.match(regexp); @@ -171,7 +172,6 @@ export const spec = { }, buildRequests: (validBidRequests, bidderRequest) => { - const storage = getStorageManager({ bidderCode: ADHASH_BIDDER_CODE }); const { gdprConsent } = bidderRequest; const bidRequests = []; const body = document.body; @@ -199,9 +199,11 @@ export const spec = { position: validBidRequests[i].adUnitCode }; let recentAds = []; + let recentAdsPrebid = []; if (storage.localStorageIsEnabled()) { const prefix = validBidRequests[i].params.prefix || 'adHash'; recentAds = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAds') || '[]'); + recentAdsPrebid = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAdsPrebid') || '[]'); } // Needed for the ad density calculation @@ -237,6 +239,7 @@ export const spec = { blockedCreatives: [], currentTimestamp: (new Date().getTime() / 1000) | 0, recentAds: recentAds, + recentAdsPrebid: recentAdsPrebid, GDPRApplies: gdprConsent ? gdprConsent.gdprApplies : null, GDPR: gdprConsent ? gdprConsent.consentString : null, servedAdsCount: window.adsCount, @@ -263,6 +266,19 @@ export const spec = { return []; } + if (storage.localStorageIsEnabled()) { + const prefix = request.bidRequest.params.prefix || 'adHash'; + let recentAdsPrebid = JSON.parse(storage.getDataFromLocalStorage(prefix + 'recentAdsPrebid') || '[]'); + recentAdsPrebid.push([ + (new Date().getTime() / 1000) | 0, + responseBody.creatives[0].advertiserId, + responseBody.creatives[0].budgetId, + responseBody.creatives[0].expectedHashes.length ? responseBody.creatives[0].expectedHashes[0] : '', + ]); + let recentAdsPrebidFinal = JSON.stringify(recentAdsPrebid.slice(-100)); + storage.setDataInLocalStorage(prefix + 'recentAdsPrebid', recentAdsPrebidFinal); + } + const publisherURL = JSON.stringify(request.bidRequest.params.platformURL); const bidderURL = request.bidRequest.params.bidderURL || 'https://bidder.adhash.com'; const oneTimeId = request.bidRequest.adUnitCode + Math.random().toFixed(16).replace('0.', '.'); diff --git a/modules/adkernelAdnBidAdapter.js b/modules/adkernelAdnBidAdapter.js index 81067a3efcf..ae5528f2aeb 100644 --- a/modules/adkernelAdnBidAdapter.js +++ b/modules/adkernelAdnBidAdapter.js @@ -2,6 +2,7 @@ import {deepAccess, deepSetValue, isArray, isNumber, isStr, logInfo, parseSizesI import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; +import {getBidFloor} from '../libraries/adkernelUtils/adkernelUtils.js' const DEFAULT_ADKERNEL_DSP_DOMAIN = 'tag.adkernel.com'; const DEFAULT_MIMES = ['video/mp4', 'video/webm', 'application/x-shockwave-flash', 'application/javascript']; @@ -143,18 +144,6 @@ function fillBidMeta(bid, tag) { } } -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, diff --git a/modules/adkernelBidAdapter.js b/modules/adkernelBidAdapter.js index 0c353e4332a..414d1ca28e9 100644 --- a/modules/adkernelBidAdapter.js +++ b/modules/adkernelBidAdapter.js @@ -20,6 +20,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {getBidFloor} from '../libraries/adkernelUtils/adkernelUtils.js' /** * In case you're AdKernel whitelable platform's client who needs branded adapter to @@ -83,7 +84,6 @@ export const spec = { {code: 'unibots'}, {code: 'ergadx'}, {code: 'turktelekom'}, - {code: 'felixads'}, {code: 'motionspots'}, {code: 'sonic_twist'}, {code: 'displayioads'}, @@ -94,7 +94,15 @@ export const spec = { {code: 'adpluto'}, {code: 'headbidder'}, {code: 'digiad'}, - {code: 'monetix'} + {code: 'monetix'}, + {code: 'hyperbrainz'}, + {code: 'voisetech'}, + {code: 'global_sun'}, + {code: 'rxnetwork'}, + {code: 'revbid'}, + {code: 'spinx', gvlid: 1308}, + {code: 'oppamedia'}, + {code: 'pixelpluses', gvlid: 1209} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -110,7 +118,9 @@ export const spec = { !isNaN(Number(bidRequest.params.zoneId)) && bidRequest.params.zoneId > 0 && bidRequest.mediaTypes && - (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video || (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native))); + (bidRequest.mediaTypes.banner || bidRequest.mediaTypes.video || + (bidRequest.mediaTypes.native && validateNativeAdUnit(bidRequest.mediaTypes.native)) + ); }, /** @@ -245,18 +255,6 @@ function groupImpressionsByHostZone(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(s) for single adunit * @param bidRequest {BidRequest} @@ -268,7 +266,7 @@ function buildImps(bidRequest, secure) { 'tagid': bidRequest.adUnitCode }; if (secure) { - imp.secure = 1; + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; } var sizes = []; let mediaTypes = bidRequest.mediaTypes; diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index e31ea290e11..0a953584e26 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -28,6 +28,7 @@ import { parseUrl } from '../src/utils.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; const MODULE = 'adlooxAnalyticsAdapter'; @@ -232,6 +233,7 @@ analyticsAdapter[`handle_${EVENTS.AUCTION_END}`] = function(auctionDetails) { link.setAttribute('href', `${uri.protocol}://${uri.host}${uri.pathname}`); link.setAttribute('rel', 'preload'); link.setAttribute('as', 'script'); + // TODO fix rules violation insertElement(link); } @@ -261,7 +263,7 @@ analyticsAdapter[`handle_${EVENTS.BID_WON}`] = function(bid) { [ 'creatype', '%%creatype%%' ] ]); - loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), 'adloox'); + loadExternalScript(analyticsAdapter.url(`${analyticsAdapter.context.js}#`, params, bid), MODULE_TYPE_ANALYTICS, 'adloox'); } adapterManager.registerAnalyticsAdapter({ diff --git a/modules/adlooxRtdProvider.js b/modules/adlooxRtdProvider.js index 727dc84e399..1545588676d 100644 --- a/modules/adlooxRtdProvider.js +++ b/modules/adlooxRtdProvider.js @@ -106,7 +106,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { // buildUrl creates PHP style multi-parameters and includes undefined... (╯°□°)╯ ┻━┻ const url = buildUrl(mergeDeep(parseUrl(`${API_ORIGIN}/q`), { search: { - 'v': `pbjs-${getGlobal().version}`, + 'v': 'pbjs-v' + '$prebid.version$', 'c': config.params.clientid, 'p': config.params.platformid, 't': config.params.tagid, diff --git a/modules/admanBidAdapter.js b/modules/admanBidAdapter.js index b78737722bd..6778e536a1b 100644 --- a/modules/admanBidAdapter.js +++ b/modules/admanBidAdapter.js @@ -1,207 +1,51 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; const GVLID = 149; const BIDDER_CODE = 'adman'; const AD_URL = 'https://pub.admanmedia.com/?c=o&m=multi'; -const URL_SYNC = 'https://sync.admanmedia.com'; +const SYNC_URL = 'https://sync.admanmedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid['mediaType']) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + placement.traffic = placement.adFormat; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } +}; - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); + const content = deepAccess(bidderRequest, 'ortb2.site.content', config.getAnyConfig('ortb2.site.content')); -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); + if (content) { + request.data.content = content; } -} + + return request; +}; export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.placementId)); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const content = deepAccess(bidderRequest, 'ortb2.site.content', config.getAnyConfig('ortb2.site.content')); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - 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 - }; - request.language.indexOf('-') != -1 && (request.language = request.language.split('-')[0]) - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - if (content) { - request.content = content; - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const { params, bidId, mediaTypes } = bid; - - const placement = { - placementId: params.placementId, - bidId, - eids: [], - bidFloor: getBidFloor(bid) - } - - if (bid.transactionId) { - placement.ext = placement.ext || {}; - placement.ext.tid = bid.transactionId; - } - - if (bid.schain) { - placement.schain = bid.schain; - } - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); - getUserId(placement.eids, bid.userId.idx, 'idx.lat'); - } - - if (mediaTypes?.[BANNER]) { - placement.traffic = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes?.[VIDEO]) { - placement.traffic = 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; - } - - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - serverResponse = serverResponse.body; - 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); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = URL_SYNC + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } - + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/admaticBidAdapter.js b/modules/admaticBidAdapter.js index 6c268b2d382..ed38cc975e8 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -3,6 +3,8 @@ 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 {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; +import { interpretNativeAd } from '../libraries/precisoUtils/bidNativeUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -10,28 +12,6 @@ import { Renderer } from '../src/Renderer.js'; * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest */ -export const OPENRTB = { - NATIVE: { - 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, - }, - } -}; - let SYNC_URL = ''; const BIDDER_CODE = 'admatic'; const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; @@ -40,7 +20,11 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ - {code: 'pixad', gvlid: 1281} + {code: 'admaticde', gvlid: 1281}, + {code: 'pixad', gvlid: 1281}, + {code: 'monetixads', gvlid: 1281}, + {code: 'netaddiction', gvlid: 1281}, + {code: 'adt', gvlid: 779} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -71,8 +55,7 @@ export const spec = { const bids = validBidRequests.map(buildRequestObject); const ortb = bidderRequest.ortb2; const networkId = getValue(validBidRequests[0].params, 'networkId'); - const host = getValue(validBidRequests[0].params, 'host'); - const currency = config.getConfig('currency.adServerCurrency') || 'TRY'; + let host = getValue(validBidRequests[0].params, 'host'); const bidderName = validBidRequests[0].bidder; const payload = { @@ -87,7 +70,6 @@ export const spec = { }, imp: bids, ext: { - cur: currency, bidder: bidderName }, schain: {}, @@ -102,6 +84,10 @@ export const spec = { tmax: parseInt(tmax) }; + if (config.getConfig('currency.adServerCurrency')) { + payload.ext.cur = config.getConfig('currency.adServerCurrency'); + } + if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { const consentStr = (bidderRequest.gdprConsent.consentString) ? bidderRequest.gdprConsent.consentString.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '') : ''; @@ -140,14 +126,27 @@ export const spec = { if (payload) { switch (bidderName) { + case 'netaddiction': + SYNC_URL = 'https://static.cdn.netaddiction.tech/netaddiction/sync.html'; + break; + case 'monetixads': + SYNC_URL = 'https://static.cdn.monetixads.com/monetixads/sync.html'; + break; case 'pixad': SYNC_URL = 'https://static.cdn.pixad.com.tr/sync.html'; break; + case 'admaticde': + SYNC_URL = 'https://static.cdn.admatic.de/admaticde/sync.html'; + break; + case 'adt': + SYNC_URL = 'https://static.cdn.adtarget.org/adt/sync.html'; + break; default: - SYNC_URL = 'https://cdn.serve.admatic.com.tr/showad/sync.html'; + SYNC_URL = 'https://static.cdn.admatic.com.tr/sync.html'; break; } + host = host?.replace('https://', '')?.replace('http://', '')?.replace('/', ''); return { method: 'POST', url: `https://${host}/pb`, data: payload, options: { contentType: 'application/json' } }; } }, @@ -155,27 +154,8 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = {}; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } - - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } - - if (gppConsent?.gppString) { - params['gpp'] = gppConsent.gppString; - params['gpp_sid'] = gppConsent.applicableSections?.toString(); - } - - params = Object.keys(params).length ? `?${formatQS(params)}` : ''; + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); + params = Object.keys(params).length ? `&${formatQS(params)}` : ''; hasSynced = true; return { @@ -203,7 +183,7 @@ export const spec = { cpm: bid.price, width: bid.width, height: bid.height, - currency: body.cur || 'TRY', + currency: body.cur, netRevenue: true, creativeId: bid.creative_id, meta: { @@ -420,45 +400,6 @@ function concatSizes(bid) { } } -function interpretNativeAd(adm) { - const native = JSON.parse(adm).native; - const result = { - clickUrl: encodeURI(native.link.url), - impressionTrackers: native.imptrackers - }; - native.assets.forEach(asset => { - switch (asset.id) { - case OPENRTB.NATIVE.ASSET_ID.TITLE: - result.title = asset.title.text; - break; - case OPENRTB.NATIVE.ASSET_ID.IMAGE: - result.image = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.ICON: - result.icon = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.BODY: - result.body = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.SPONSORED: - result.sponsoredBy = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.CTA: - result.cta = asset.data.value; - break; - } - }); - return result; -} - function _validateId(id) { return (parseInt(id) > 0); } diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index de7b941d614..1570a36c5f0 100644 --- a/modules/admixerBidAdapter.js +++ b/modules/admixerBidAdapter.js @@ -1,4 +1,4 @@ -import {isStr, logError} from '../src/utils.js'; +import {isStr, logError, isFn, deepAccess} 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'; @@ -13,7 +13,7 @@ const ALIASES = [ {code: 'futureads', endpoint: 'https://ads.futureads.io/prebid.1.2.aspx'}, {code: 'smn', endpoint: 'https://ads.smn.rs/prebid.1.2.aspx'}, {code: 'admixeradx', endpoint: 'https://inv-nets.admixer.net/adxprebid.1.2.aspx'}, - {code: 'admixerwl', endpoint: 'https://inv-nets-adxwl.admixer.com/adxwlprebid.aspx'}, + 'rtbstack' ]; export const spec = { code: BIDDER_CODE, @@ -23,8 +23,8 @@ export const spec = { * Determines whether or not the given bid request is valid. */ isBidRequestValid: function (bid) { - return bid.bidder === 'admixerwl' - ? !!bid.params.clientId && !!bid.params.endpointId + return bid.bidder === 'rtbstack' + ? !!bid.params.tagId : !!bid.params.zone; }, /** @@ -51,8 +51,12 @@ export const spec = { }; let endpointUrl; if (bidderRequest) { - const {bidderCode} = bidderRequest; - endpointUrl = config.getConfig(`${bidderCode}.endpoint_url`); + // checks if there is specified any endpointUrl in bidder config + endpointUrl = config.getConfig('bidderURL'); + if (!endpointUrl && bidderRequest.bidderCode === 'rtbstack') { + logError('The bidderUrl config is required for RTB Stack bids. Please set it with setBidderConfig() for "rtbstack".'); + return; + } // TODO: is 'page' the right value here? if (bidderRequest.refererInfo?.page) { payload.referrer = encodeURIComponent(bidderRequest.refererInfo.page); @@ -67,22 +71,22 @@ export const spec = { if (bidderRequest.uspConsent) { payload.uspConsent = bidderRequest.uspConsent; } - let bidFloor = getBidFloor(bidderRequest); - if (bidFloor) { - payload.bidFloor = bidFloor; - } } validRequest.forEach((bid) => { let imp = {}; Object.keys(bid).forEach(key => imp[key] = bid[key]); imp.ortb2 && delete imp.ortb2; + let bidFloor = getBidFloor(bid); + if (bidFloor) { + imp.bidFloor = bidFloor; + } payload.imps.push(imp); }); let urlForRequest = endpointUrl || getEndpointUrl(bidderRequest.bidderCode) return { method: 'POST', - url: bidderRequest.bidderCode === 'admixerwl' ? `${urlForRequest}?client=${payload.imps[0]?.params?.clientId}` : urlForRequest, + url: urlForRequest, data: payload, }; }, @@ -113,19 +117,26 @@ export const spec = { return pixels; } }; + function getEndpointUrl(code) { return find(ALIASES, (val) => val.code === code)?.endpoint || ENDPOINT_URL; } + function getBidFloor(bid) { + if (!isFn(bid.getFloor)) { + return deepAccess(bid, 'params.bidFloor', 0); + } + try { const bidFloor = bid.getFloor({ currency: 'USD', mediaType: '*', size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } catch (_) { return 0; } } + registerBidder(spec); diff --git a/modules/admixerBidAdapter.md b/modules/admixerBidAdapter.md index 64f8dd64ee4..097e7feb95e 100644 --- a/modules/admixerBidAdapter.md +++ b/modules/admixerBidAdapter.md @@ -51,7 +51,7 @@ Please use ```admixer``` as the bidder code. ]; ``` -### AdmixerWL Test Parameters +### RTB Stack Test Parameters ``` var adUnits = [ { @@ -59,10 +59,9 @@ Please use ```admixer``` as the bidder code. sizes: [[300, 250]], // a display size bids: [ { - bidder: "admixer", + bidder: "rtbstack", params: { - endpointId: 41512, - clientId: 62 + tagId: 41512 } } ] @@ -71,10 +70,9 @@ Please use ```admixer``` as the bidder code. sizes: [[300, 50]], // a mobile size bids: [ { - bidder: "admixer", + bidder: "rtbstack", params: { - endpointId: 41512, - clientId: 62 + tagId: 41512 } } ] @@ -84,10 +82,9 @@ Please use ```admixer``` as the bidder code. mediaType: 'video', bids: [ { - bidder: "admixer", + bidder: "rtbstack", params: { - endpointId: 41512, - clientId: 62 + tagId: 41512 } } ] diff --git a/modules/adnuntiusBidAdapter.js b/modules/adnuntiusBidAdapter.js index 060f87c0f9c..cce1b5332ad 100644 --- a/modules/adnuntiusBidAdapter.js +++ b/modules/adnuntiusBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { isStr, deepAccess } from '../src/utils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -12,16 +12,19 @@ const BIDDER_CODE_DEAL_ALIASES = [1, 2, 3, 4, 5].map(num => { const ENDPOINT_URL = 'https://ads.adnuntius.delivery/i'; const ENDPOINT_URL_EUROPE = 'https://europe.delivery.adnuntius.com/i'; const GVLID = 855; -const DEFAULT_VAST_VERSION = 'vast4' +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const MAXIMUM_DEALS_LIMIT = 5; const VALID_BID_TYPES = ['netBid', 'grossBid']; const METADATA_KEY = 'adn.metaData'; const METADATA_KEY_SEPARATOR = '@@@'; export const misc = { - getUnixTimestamp: function (addDays, asMinutes) { - const multiplication = addDays / (asMinutes ? 1440 : 1); - return Date.now() + (addDays && addDays > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); + findHighestPrice: function(arr, bidType) { + return arr.reduce((highest, cur) => { + const currentBid = cur[bidType]; + const highestBid = highest[bidType] + return currentBid.currency === highestBid.currency && currentBid.amount > highestBid.amount ? cur : highest; + }, arr[0]); } }; @@ -50,11 +53,11 @@ const storageTool = (function () { if (datum.key === 'voidAuIds' && Array.isArray(datum.value)) { return true; } - return datum.key && datum.value && datum.exp && datum.exp > misc.getUnixTimestamp() && (!network || network === datum.network); + return datum.key && datum.value && datum.exp && datum.exp > getUnixTimestampFromNow() && (!network || network === datum.network); }) : []; const voidAuIdsEntry = filteredEntries.find(entry => entry.key === 'voidAuIds'); if (voidAuIdsEntry) { - const now = misc.getUnixTimestamp(); + const now = getUnixTimestampFromNow(); voidAuIdsEntry.value = voidAuIdsEntry.value.filter(voidAuId => voidAuId.auId && voidAuId.exp > now); if (!voidAuIdsEntry.value.length) { filteredEntries = filteredEntries.filter(entry => entry.key !== 'voidAuIds'); @@ -73,7 +76,7 @@ const storageTool = (function () { const notNewExistingAuIds = currentVoidAuIds.filter(auIdObj => { return newAuIds.indexOf(auIdObj.value) < -1; }) || []; - const oneDayFromNow = misc.getUnixTimestamp(1); + const oneDayFromNow = getUnixTimestampFromNow(1); const apiIdsArray = newAuIds.map(auId => { return { exp: oneDayFromNow, auId: auId }; }) || []; @@ -86,7 +89,7 @@ const storageTool = (function () { if (key !== 'voidAuIds') { metaAsObj[key + METADATA_KEY_SEPARATOR + network] = { value: apiRespMetadata[key], - exp: misc.getUnixTimestamp(100), + exp: getUnixTimestampFromNow(100), network: network } } @@ -130,31 +133,6 @@ const storageTool = (function () { return (meta && meta.usi) ? meta.usi : false } - const getSegmentsFromOrtb = function (ortb2) { - const userData = deepAccess(ortb2, 'user.data'); - let segments = []; - if (userData) { - userData.forEach(userdat => { - if (userdat.segment) { - segments.push(...userdat.segment.map((segment) => { - if (isStr(segment)) return segment; - if (isStr(segment.id)) return segment.id; - }).filter((seg) => !!seg)); - } - }); - } - return segments - } - - const getKvsFromOrtb = function (ortb2) { - const siteData = deepAccess(ortb2, 'site.ext.data'); - if (siteData) { - return siteData - } else { - return null - } - } - return { refreshStorage: function (bidderRequest) { const ortb2 = bidderRequest.ortb2 || {}; @@ -171,25 +149,73 @@ const storageTool = (function () { return voidAuId.auId; }); } - metaInternal.segments = getSegmentsFromOrtb(ortb2); - metaInternal.kv = getKvsFromOrtb(ortb2); }, saveToStorage: function (serverData, network) { setMetaInternal(serverData, network); }, getUrlRelatedData: function () { // getting the URL information is theoretically not network-specific - const { segments, kv, usi, voidAuIdsArray } = metaInternal; - return { segments, kv, usi, voidAuIdsArray }; + const { usi, voidAuIdsArray } = metaInternal; + return { usi, voidAuIdsArray }; }, getPayloadRelatedData: function (network) { // getting the payload data should be network-specific - const { segments, kv, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); + const { segments, usi, userId, voidAuIdsArray, voidAuIds, ...payloadRelatedData } = getMetaDataFromLocalStorage(network).reduce((a, entry) => ({ ...a, [entry.key]: entry.value }), {}); return payloadRelatedData; } }; })(); +const targetingTool = (function() { + const getSegmentsFromOrtb = function(bidderRequest) { + const userData = deepAccess(bidderRequest.ortb2 || {}, 'user.data'); + let segments = []; + if (userData) { + userData.forEach(userdat => { + if (userdat.segment) { + segments.push(...userdat.segment.map((segment) => { + if (isStr(segment)) return segment; + if (isStr(segment.id)) return segment.id; + }).filter((seg) => !!seg)); + } + }); + } + return segments + }; + + const getKvsFromOrtb = function(bidderRequest) { + return deepAccess(bidderRequest.ortb2 || {}, 'site.ext.data'); + }; + + return { + addSegmentsToUrlData: function (validBids, bidderRequest, existingUrlRelatedData) { + let segments = getSegmentsFromOrtb(bidderRequest || {}); + + for (let i = 0; i < validBids.length; i++) { + const bid = validBids[i]; + const targeting = bid.params.targeting || {}; + if (Array.isArray(targeting.segments)) { + segments = segments.concat(targeting.segments); + delete bid.params.targeting.segments; + } + } + + existingUrlRelatedData.segments = segments; + }, + mergeKvsFromOrtb: function(bidTargeting, bidderRequest) { + const kv = getKvsFromOrtb(bidderRequest || {}); + if (isEmpty(kv)) { + return; + } + if (bidTargeting.kv && !Array.isArray(bidTargeting.kv)) { + bidTargeting.kv = convertObjectToArray(bidTargeting.kv); + } + bidTargeting.kv = bidTargeting.kv || []; + bidTargeting.kv = bidTargeting.kv.concat(convertObjectToArray(kv)); + } + } +})(); + const validateBidType = function (bidTypeOption) { return VALID_BID_TYPES.indexOf(bidTypeOption || '') > -1 ? bidTypeOption : 'bid'; } @@ -200,7 +226,7 @@ export const spec = { code: BIDDER_CODE, aliases: BIDDER_CODE_DEAL_ALIASES, gvlid: GVLID, - supportedMediaTypes: [BANNER, VIDEO], + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function (bid) { // The auId MUST be a hexadecimal string const validAuId = AU_ID_REGEX.test(bid.params.auId); @@ -227,11 +253,13 @@ export const spec = { storageTool.refreshStorage(bidderRequest); const urlRelatedMetaData = storageTool.getUrlRelatedData(); + targetingTool.addSegmentsToUrlData(validBidRequests, bidderRequest, urlRelatedMetaData); if (urlRelatedMetaData.segments.length > 0) queryParamsAndValues.push('segments=' + urlRelatedMetaData.segments.join(',')); if (urlRelatedMetaData.usi) queryParamsAndValues.push('userId=' + urlRelatedMetaData.usi); const bidderConfig = config.getConfig(); if (bidderConfig.useCookie === false) queryParamsAndValues.push('noCookies=true'); + if (bidderConfig.advertiserTransparency === true) queryParamsAndValues.push('advertiserTransparency=true'); if (bidderConfig.maxDeals > 0) queryParamsAndValues.push('ds=' + Math.min(bidderConfig.maxDeals, MAXIMUM_DEALS_LIMIT)); const bidRequests = {}; @@ -245,38 +273,61 @@ export const spec = { } let network = bid.params.network || 'network'; - if (bid.mediaTypes && bid.mediaTypes.video && bid.mediaTypes.video.context !== 'outstream') { - network += '_video' - } - bidRequests[network] = bidRequests[network] || []; bidRequests[network].push(bid); networks[network] = networks[network] || {}; networks[network].adUnits = networks[network].adUnits || []; - if (bidderRequest && bidderRequest.refererInfo) networks[network].context = bidderRequest.refererInfo.page; + + const refererInfo = bidderRequest && bidderRequest.refererInfo ? bidderRequest.refererInfo : {}; + if (refererInfo.page) { + networks[network].context = bidderRequest.refererInfo.page; + } + if (refererInfo.canonicalUrl) { + networks[network].canonical = bidderRequest.refererInfo.canonicalUrl; + } const payloadRelatedData = storageTool.getPayloadRelatedData(bid.params.network); if (Object.keys(payloadRelatedData).length > 0) { networks[network].metaData = payloadRelatedData; } - const targeting = bid.params.targeting || {}; - if (urlRelatedMetaData.kv) targeting.kv = urlRelatedMetaData.kv; - const adUnit = { ...targeting, auId: bid.params.auId, targetId: bid.params.targetId || bid.bidId }; - const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); - if (maxDeals > 0) { - adUnit.maxDeals = maxDeals; + const bidTargeting = {...bid.params.targeting || {}}; + targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); + const mediaTypes = bid.mediaTypes || {}; + const validMediaTypes = SUPPORTED_MEDIA_TYPES.filter(mt => { + return mediaTypes[mt]; + }) || []; + if (validMediaTypes.length === 0) { + // banner ads by default if nothing specified, dimensions to be derived from the ad unit within adnuntius system + validMediaTypes.push(BANNER); } - if (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) adUnit.dimensions = bid.mediaTypes.banner.sizes - networks[network].adUnits.push(adUnit); + const isSingleFormat = validMediaTypes.length === 1; + validMediaTypes.forEach(mediaType => { + const mediaTypeData = mediaTypes[mediaType]; + if (mediaType === VIDEO && mediaTypeData && mediaTypeData.context === 'outstream') { + return; + } + const targetId = (bid.params.targetId || bid.bidId) + (isSingleFormat || mediaType === BANNER ? '' : ('-' + mediaType)); + const adUnit = {...bidTargeting, auId: bid.params.auId, targetId: targetId}; + if (mediaType === VIDEO) { + adUnit.adType = 'VAST'; + } + const maxDeals = Math.max(0, Math.min(bid.params.maxDeals || 0, MAXIMUM_DEALS_LIMIT)); + if (maxDeals > 0) { + adUnit.maxDeals = maxDeals; + } + if (mediaType === BANNER && mediaTypeData && mediaTypeData.sizes) { + adUnit.dimensions = mediaTypeData.sizes; + } + networks[network].adUnits.push(adUnit); + }); } const requests = []; const networkKeys = Object.keys(networks); for (let j = 0; j < networkKeys.length; j++) { const network = networkKeys[j]; - if (network.indexOf('_video') > -1) { queryParamsAndValues.push('tt=' + DEFAULT_VAST_VERSION) } const requestURL = gdprApplies ? ENDPOINT_URL_EUROPE : ENDPOINT_URL requests.push({ method: 'POST', @@ -293,7 +344,7 @@ export const spec = { if (serverResponse.body.metaData) { storageTool.saveToStorage(serverResponse.body.metaData, serverResponse.body.network); } - const adUnits = serverResponse.body.adUnits; + const responseAdUnits = serverResponse.body.adUnits; let validatedBidType = validateBidType(config.getConfig().bidType); if (bidRequest.bid) { @@ -339,6 +390,35 @@ export const spec = { return adResponse; } + const highestYieldingAdUnits = []; + if (responseAdUnits.length === 1) { + highestYieldingAdUnits.push(responseAdUnits[0]); + } else if (responseAdUnits.length > 1) { + bidRequest.bid.forEach((resp) => { + const multiFormatAdUnits = []; + SUPPORTED_MEDIA_TYPES.forEach((mediaType) => { + const suffix = mediaType === BANNER ? '' : '-' + mediaType; + const targetId = (resp?.params?.targetId || resp.bidId) + suffix; + + const au = responseAdUnits.find((rAu) => { + return rAu.targetId === targetId && rAu.matchedAdCount > 0; + }); + if (au) { + multiFormatAdUnits.push(au); + } + }); + if (multiFormatAdUnits.length > 0) { + const highestYield = multiFormatAdUnits.length === 1 ? multiFormatAdUnits[0] : multiFormatAdUnits.reduce((highest, cur) => { + const highestBid = misc.findHighestPrice(highest.ads, validatedBidType)[validatedBidType]; + const curBid = misc.findHighestPrice(cur.ads, validatedBidType)[validatedBidType]; + return curBid.currency === highestBid.currency && curBid.amount > highestBid.amount ? cur : highest; + }, multiFormatAdUnits[0]); + highestYield.targetId = resp.bidId; + highestYieldingAdUnits.push(highestYield); + } + }); + } + const bidsById = bidRequest.bid.reduce((response, bid) => { return { ...response, @@ -346,7 +426,7 @@ export const spec = { }; }, {}); - const hasBidAdUnits = adUnits.filter((au) => { + const hasBidAdUnits = highestYieldingAdUnits.filter((au) => { const bid = bidsById[au.targetId]; if (bid && bid.bidder && BIDDER_CODE_DEAL_ALIASES.indexOf(bid.bidder) < 0) { return au.matchedAdCount > 0; @@ -356,7 +436,7 @@ export const spec = { return false; } }); - const hasDealsAdUnits = adUnits.filter((au) => { + const hasDealsAdUnits = highestYieldingAdUnits.filter((au) => { return au.deals && au.deals.length > 0; }); diff --git a/modules/adomikAnalyticsAdapter.js b/modules/adomikAnalyticsAdapter.js deleted file mode 100644 index d6e1547cce8..00000000000 --- a/modules/adomikAnalyticsAdapter.js +++ /dev/null @@ -1,262 +0,0 @@ -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import {EVENTS} from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {logInfo} from '../src/utils.js'; -import {find, findIndex} from '../src/polyfill.js'; - -// Events used in adomik analytics adapter. -const auctionInit = EVENTS.AUCTION_INIT; -const auctionEnd = EVENTS.AUCTION_END; -const bidRequested = EVENTS.BID_REQUESTED; -const bidResponse = EVENTS.BID_RESPONSE; -const bidWon = EVENTS.BID_WON; -const bidTimeout = EVENTS.BID_TIMEOUT; -const ua = navigator.userAgent; - -var _sampled = true; - -let adomikAdapter = Object.assign(adapter({}), - { - // Track every event needed - track({ eventType, args }) { - switch (eventType) { - case auctionInit: - adomikAdapter.initializeBucketEvents() - adomikAdapter.currentContext.id = args.auctionId - break; - - case bidTimeout: - adomikAdapter.currentContext.timeouted = true; - break; - - case bidResponse: - adomikAdapter.saveBidResponse(args); - break; - - case bidWon: - args.id = args.adId; - args.placementCode = args.adUnitCode; - adomikAdapter.sendWonEvent(args); - break; - - case bidRequested: - args.bids.forEach(function(bid) { - adomikAdapter.bucketEvents.push({ - type: 'request', - event: { - bidder: bid.bidder.toUpperCase(), - placementCode: bid.adUnitCode - } - }); - }); - break; - - case auctionEnd: - if (adomikAdapter.bucketEvents.length > 0) { - adomikAdapter.sendTypedEvent(); - } - break; - } - } - } -); - -adomikAdapter.initializeBucketEvents = function() { - adomikAdapter.bucketEvents = []; -} - -adomikAdapter.saveBidResponse = function(args) { - let responseSaved = adomikAdapter.bucketEvents.find((bucketEvent) => - bucketEvent.type == 'response' && bucketEvent.event.id == args.id - ); - if (responseSaved) { return true; } - adomikAdapter.bucketEvents.push({ - type: 'response', - event: adomikAdapter.buildBidResponse(args) - }); -} - -adomikAdapter.maxPartLength = function () { - return (ua.includes(' MSIE ')) ? 1600 : 60000; -}; - -adomikAdapter.sendTypedEvent = function() { - let [testId, testValue] = adomikAdapter.getKeyValues(); - const groupedTypedEvents = adomikAdapter.buildTypedEvents(); - - const bulkEvents = { - testId: testId, - testValue: testValue, - uid: adomikAdapter.currentContext.uid, - ahbaid: adomikAdapter.currentContext.id, - hostname: window.location.hostname, - sampling: adomikAdapter.currentContext.sampling, - eventsByPlacementCode: groupedTypedEvents.map(function(typedEventsByType) { - let sizes = []; - const eventKeys = ['request', 'response', 'winner']; - let events = {}; - - eventKeys.forEach((eventKey) => { - events[`${eventKey}s`] = []; - if (typedEventsByType[eventKey] !== undefined) { - typedEventsByType[eventKey].forEach((typedEvent) => { - if (typedEvent.event.size !== undefined) { - const size = adomikAdapter.sizeUtils.handleSize(sizes, typedEvent.event.size); - if (size !== null) { - sizes = [...sizes, size]; - } - } - events[`${eventKey}s`] = [...events[`${eventKey}s`], typedEvent.event]; - }); - } - }); - - return { - placementCode: typedEventsByType.placementCode, - sizes, - events - }; - }) - }; - - const stringBulkEvents = JSON.stringify(bulkEvents) - logInfo('Events sent to adomik prebid analytic ' + stringBulkEvents); - - const encodedBuf = window.btoa(stringBulkEvents); - - const encodedUri = encodeURIComponent(encodedBuf); - const maxLength = adomikAdapter.maxPartLength(); - const splittedUrl = encodedUri.match(new RegExp(`.{1,${maxLength}}`, 'g')); - - splittedUrl.forEach((split, i) => { - const partUrl = `${split}&id=${adomikAdapter.currentContext.id}&part=${i}&on=${splittedUrl.length - 1}`; - const img = new Image(1, 1); - img.src = 'https://' + adomikAdapter.currentContext.url + '/?q=' + partUrl; - }) -}; - -adomikAdapter.sendWonEvent = function (wonEvent) { - let [testId, testValue] = adomikAdapter.getKeyValues(); - let keyValues = { testId: testId, testValue: testValue }; - let samplingInfo = { sampling: adomikAdapter.currentContext.sampling }; - wonEvent = { ...adomikAdapter.buildBidResponse(wonEvent), ...keyValues, ...samplingInfo }; - - const stringWonEvent = JSON.stringify(wonEvent); - logInfo('Won event sent to adomik prebid analytic ' + stringWonEvent); - - const encodedBuf = window.btoa(stringWonEvent); - const encodedUri = encodeURIComponent(encodedBuf); - const img = new Image(1, 1); - img.src = `https://${adomikAdapter.currentContext.url}/?q=${encodedUri}&id=${adomikAdapter.currentContext.id}&won=true`; -} - -adomikAdapter.buildBidResponse = function (bid) { - return { - bidder: bid.bidderCode.toUpperCase(), - placementCode: bid.adUnitCode, - id: bid.adId, - status: (bid.statusMessage === 'Bid available') ? 'VALID' : 'EMPTY_OR_ERROR', - cpm: parseFloat(bid.cpm), - size: { - width: Number(bid.width), - height: Number(bid.height) - }, - timeToRespond: bid.timeToRespond, - afterTimeout: adomikAdapter.currentContext.timeouted - }; -} - -adomikAdapter.sizeUtils = { - sizeAlreadyExists: (sizes, typedEventSize) => { - return find(sizes, (size) => size.height === typedEventSize.height && size.width === typedEventSize.width); - }, - formatSize: (typedEventSize) => { - return { - width: Number(typedEventSize.width), - height: Number(typedEventSize.height) - }; - }, - handleSize: (sizes, typedEventSize) => { - let formattedSize = null; - if (adomikAdapter.sizeUtils.sizeAlreadyExists(sizes, typedEventSize) === undefined) { - formattedSize = adomikAdapter.sizeUtils.formatSize(typedEventSize); - } - return formattedSize; - } -}; - -adomikAdapter.buildTypedEvents = function () { - const groupedTypedEvents = []; - adomikAdapter.bucketEvents.forEach(function(typedEvent, i) { - const [placementCode, type] = [typedEvent.event.placementCode, typedEvent.type]; - let existTypedEvent = findIndex(groupedTypedEvents, (groupedTypedEvent) => groupedTypedEvent.placementCode === placementCode); - - if (existTypedEvent === -1) { - groupedTypedEvents.push({ - placementCode: placementCode, - [type]: [typedEvent] - }); - existTypedEvent = groupedTypedEvents.length - 1; - } - - if (groupedTypedEvents[existTypedEvent][type]) { - groupedTypedEvents[existTypedEvent][type] = [...groupedTypedEvents[existTypedEvent][type], typedEvent]; - } else { - groupedTypedEvents[existTypedEvent][type] = [typedEvent]; - } - }); - - return groupedTypedEvents; -} - -adomikAdapter.getKeyValues = function () { - let preventTest = sessionStorage.getItem(window.location.hostname + '_NoAdomikTest') - let inScope = sessionStorage.getItem(window.location.hostname + '_AdomikTestInScope') - let keyValues = JSON.parse(sessionStorage.getItem(window.location.hostname + '_AdomikTest')) - let testId; - let testValue; - if (typeof (keyValues) === 'object' && keyValues != undefined && !preventTest && inScope) { - testId = keyValues.testId - testValue = keyValues.testOptionLabel - } - return [testId, testValue] -} - -adomikAdapter.enable = function(options) { - adomikAdapter.currentContext = { - uid: options.id, - url: options.url, - id: '', - timeouted: false, - sampling: options.sampling - } - logInfo('Adomik Analytics enabled with config', options); - adomikAdapter.adapterEnableAnalytics(options); -}; - -adomikAdapter.checkOptions = function(options) { - if (typeof options !== 'undefined') { - if (options.id && options.url) { adomikAdapter.enable(options); } else { logInfo('Adomik Analytics disabled because id and/or url is missing from config', options); } - } else { logInfo('Adomik Analytics disabled because config is missing'); } -}; - -adomikAdapter.checkSampling = function(options) { - _sampled = typeof options === 'undefined' || - typeof options.sampling === 'undefined' || - (options.sampling > 0 && Math.random() < parseFloat(options.sampling)); - if (_sampled) { adomikAdapter.checkOptions(options) } else { logInfo('Adomik Analytics ignored for sampling', options.sampling); } -}; - -adomikAdapter.adapterEnableAnalytics = adomikAdapter.enableAnalytics; - -adomikAdapter.enableAnalytics = function ({ provider, options }) { - logInfo('Adomik Analytics enableAnalytics', provider); - adomikAdapter.checkSampling(options); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: adomikAdapter, - code: 'adomik' -}); - -export default adomikAdapter; diff --git a/modules/adotBidAdapter.js b/modules/adotBidAdapter.js index 9f2810e13df..18c7e9265bf 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -6,6 +6,7 @@ import {find} from '../src/polyfill.js'; import {config} from '../src/config.js'; import {OUTSTREAM} from '../src/video.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { NATIVE_ASSETS_IDS as NATIVE_ID_MAPPING, NATIVE_ASSETS as NATIVE_PLACEMENTS } from '../libraries/braveUtils/nativeAssets.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -24,6 +25,37 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; * @typedef {import('../src/adapters/bidderFactory.js').Imp} Imp */ +/** + * @typedef {Object} OpenRtbRequest + * @property {string} id - Unique request ID + * @property {Array} imp - List of impression objects + * @property {Site} site - Site information + * @property {Device} device - Device information + * @property {User} user - User information + * @property {object} regs - Regulatory data, including GDPR and COPPA + * @property {object} ext - Additional extensions, such as custom data for the bid request + * @property {number} at - Auction type, typically first-price or second-price + */ + +/** + * @typedef {Object} OpenRtbBid + * @property {string} impid - ID of the impression this bid relates to + * @property {number} price - Bid price for the impression + * @property {string} adid - Ad ID for the bid + * @property {number} [crid] - Creative ID, if available + * @property {string} [dealid] - Deal ID if the bid is part of a private marketplace deal + * @property {object} [ext] - Additional bid-specific extensions, such as media type + * @property {string} [adm] - Ad markup if it’s directly included in the bid response + * @property {string} [nurl] - Notification URL to be called when the bid wins + */ + +/** + * @typedef {Object} OpenRtbBidResponse + * @property {string} id - ID of the bid response + * @property {Array<{bid: Array}>} seatbid - Array of seat bids, each containing a list of bids + * @property {string} cur - Currency in which bid amounts are expressed + */ + const BIDDER_CODE = 'adot'; const ADAPTER_VERSION = 'v2.0.0'; const GVLID = 272; @@ -32,15 +64,6 @@ const BIDDER_URL = 'https://dsp.adotmob.com/headerbidding{PUBLISHER_PATH}/bidreq const REQUIRED_VIDEO_PARAMS = ['mimes', 'protocols']; const FIRST_PRICE = 1; const IMP_BUILDER = { banner: buildBanner, video: buildVideo, native: buildNative }; -const NATIVE_PLACEMENTS = { - title: { id: 1, name: 'title' }, - icon: { id: 2, type: 1, name: 'img' }, - image: { id: 3, type: 3, name: 'img' }, - sponsoredBy: { id: 4, name: 'data', type: 1 }, - body: { id: 5, name: 'data', type: 2 }, - cta: { id: 6, type: 12, name: 'data' } -}; -const NATIVE_ID_MAPPING = { 1: 'title', 2: 'icon', 3: 'image', 4: 'sponsoredBy', 5: 'body', 6: 'cta' }; const OUTSTREAM_VIDEO_PLAYER_URL = 'https://adserver.adotmob.com/video/player.min.js'; const BID_RESPONSE_NET_REVENUE = true; const BID_RESPONSE_TTL = 10; @@ -197,7 +220,7 @@ function buildVideo(video) { mimes: video.mimes, minduration: video.minduration, maxduration: video.maxduration, - placement: video.placement, + placement: video.plcmt, playbackmethod: video.playbackmethod, pos: video.position || 0, protocols: video.protocols, @@ -618,7 +641,7 @@ function getFloor(adUnit, size, mediaType, currency) { const floorResult = adUnit.getFloor({ currency, mediaType, size }); - return floorResult.currency === currency ? floorResult.floor : 0; + return floorResult?.currency === currency ? floorResult?.floor : 0; } /** diff --git a/modules/adpartnerBidAdapter.js b/modules/adpartnerBidAdapter.js index 471a0bba64a..504809afa87 100644 --- a/modules/adpartnerBidAdapter.js +++ b/modules/adpartnerBidAdapter.js @@ -1,6 +1,5 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { buildUrl } from '../src/utils.js' -import {ajax} from '../src/ajax.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { buildBidRequestsAndParams, postRequest, buildEndpointUrl } from '../libraries/mediaImpactUtils/index.js'; const BIDDER_CODE = 'adpartner'; export const ENDPOINT_PROTOCOL = 'https'; @@ -15,74 +14,25 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - // TODO does it make sense to fall back to window.location.href? const referer = bidderRequest?.refererInfo?.page || window.location.href; - let bidRequests = []; - let beaconParams = { - tag: [], - partner: [], - sizes: [], - referer: '' - }; - - validBidRequests.forEach(function(validBidRequest) { - let bidRequestObject = { - adUnitCode: validBidRequest.adUnitCode, - sizes: validBidRequest.sizes, - bidId: validBidRequest.bidId, - referer: referer - }; - - if (parseInt(validBidRequest.params.unitId)) { - bidRequestObject.unitId = parseInt(validBidRequest.params.unitId); - beaconParams.tag.push(validBidRequest.params.unitId); - } - - if (parseInt(validBidRequest.params.partnerId)) { - bidRequestObject.unitId = 0; - bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId); - beaconParams.partner.push(validBidRequest.params.partnerId); - } - - bidRequests.push(bidRequestObject); + // Use the common function to build bidRequests and beaconParams + const { bidRequests, beaconParams } = buildBidRequestsAndParams(validBidRequests, referer); - beaconParams.sizes.push(spec.joinSizesToString(validBidRequest.sizes)); - beaconParams.referer = encodeURIComponent(referer); - }); - - if (beaconParams.partner.length > 0) { - beaconParams.partner = beaconParams.partner.join(','); - } else { - delete beaconParams.partner; - } - - beaconParams.tag = beaconParams.tag.join(','); - beaconParams.sizes = beaconParams.sizes.join(','); - - let adPartnerRequestUrl = buildUrl({ - protocol: ENDPOINT_PROTOCOL, - hostname: ENDPOINT_DOMAIN, - pathname: ENDPOINT_PATH, - search: beaconParams - }); + const adPartnerRequestUrl = buildEndpointUrl( + ENDPOINT_PROTOCOL, + ENDPOINT_DOMAIN, + ENDPOINT_PATH, + beaconParams + ); return { method: 'POST', url: adPartnerRequestUrl, - data: JSON.stringify(bidRequests) + data: JSON.stringify(bidRequests), }; }, - joinSizesToString: function(sizes) { - let res = []; - sizes.forEach(function(size) { - res.push(size.join('x')); - }); - - return res.join('|'); - }, - interpretResponse: function (serverResponse, bidRequest) { const validBids = JSON.parse(bidRequest.data); @@ -91,15 +41,12 @@ export const spec = { } return validBids - .map(bid => ({ - bid: bid, - ad: serverResponse.body[bid.adUnitCode] - })) + .map(bid => ({ bid: bid, ad: serverResponse.body[bid.adUnitCode] })) .filter(item => item.ad) .map(item => spec.adResponse(item.bid, item.ad)); }, - adResponse: function(bid, ad) { + adResponse: function (bid, ad) { const bidObject = { requestId: bid.bidId, ad: ad.ad, @@ -110,38 +57,30 @@ export const spec = { creativeId: ad.creativeId, netRevenue: ad.netRevenue, currency: ad.currency, - winNotification: ad.winNotification - } - - bidObject.meta = {}; - if (ad.adomain && ad.adomain.length > 0) { - bidObject.meta.advertiserDomains = ad.adomain; - } + winNotification: ad.winNotification, + meta: ad.adomain && ad.adomain.length > 0 ? { advertiserDomains: ad.adomain } : {}, + }; return bidObject; }, - onBidWon: function(data) { - data.winNotification.forEach(function(unitWon) { - let adPartnerBidWonUrl = buildUrl({ - protocol: ENDPOINT_PROTOCOL, - hostname: ENDPOINT_DOMAIN, - pathname: unitWon.path - }); + onBidWon: function (data) { + data.winNotification.forEach(function (unitWon) { + const adPartnerBidWonUrl = buildEndpointUrl( + ENDPOINT_PROTOCOL, + ENDPOINT_DOMAIN, + unitWon.path + ); if (unitWon.method === 'POST') { - spec.postRequest(adPartnerBidWonUrl, JSON.stringify(unitWon.data)); + postRequest(adPartnerBidWonUrl, JSON.stringify(unitWon.data)); } }); return true; }, - postRequest(endpoint, data) { - ajax(endpoint, null, data, {method: 'POST'}); - }, - - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + getUserSyncs: function (syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { @@ -205,7 +144,6 @@ export const spec = { return syncs; }, - -} +}; registerBidder(spec); diff --git a/modules/adplayerproVideoProvider.js b/modules/adplayerproVideoProvider.js new file mode 100644 index 00000000000..826aee257ec --- /dev/null +++ b/modules/adplayerproVideoProvider.js @@ -0,0 +1,439 @@ +import { + API_FRAMEWORKS, + PLACEMENT, + PLAYBACK_METHODS, + PROTOCOLS, + VIDEO_MIME_TYPE, + VPAID_MIME_TYPE +} from '../libraries/video/constants/ortb.js'; +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + ERROR, + MUTE, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from '../libraries/video/constants/events.js'; +import {AD_PLAYER_PRO_VENDOR} from '../libraries/video/constants/vendorCodes.js'; +import {getEventHandler} from '../libraries/video/shared/eventHandler.js'; +import {submodule} from '../src/hook.js'; + +const setupFailMessage = 'Failed to instantiate the player'; + +/** + * @constructor + * @param {Object} config - videoProviderConfig + * @param {function} adPlayerPro_ + * @param {CallbackStorage} callbackStorage_ + * @param {Object} utils + * @returns {Object} - VideoProvider + */ +export function AdPlayerProProvider(config, adPlayerPro_, callbackStorage_, utils) { + const adPlayerPro = adPlayerPro_; + let player = null; + let playerVersion = null; + const playerConfig = config.playerConfig; + const divId = config.divId; + let callbackStorage = callbackStorage_; + let supportedMediaTypes = null; + let setupCompleteCallbacks = []; + let setupFailedCallbacks = []; + const MEDIA_TYPES = [ + VIDEO_MIME_TYPE.MP4, + VIDEO_MIME_TYPE.OGG, + VIDEO_MIME_TYPE.WEBM, + VIDEO_MIME_TYPE.AAC, + VIDEO_MIME_TYPE.HLS + ]; + + function init() { + if (!adPlayerPro) { + triggerSetupFailure(-1, setupFailMessage + ': player not present'); + return; + } + + // if (playerVersion < minimumSupportedPlayerVersion) { + // triggerSetupFailure(-2, setupFailMessage + ': player version not supported'); + // return; + // } + + if (!document.getElementById(divId)) { + triggerSetupFailure(-3, setupFailMessage + ': No div found with id ' + divId); + return; + } + + if (!playerConfig || !playerConfig.placementId) { + triggerSetupFailure(-4, setupFailMessage + ': placementId is required in playerConfig'); + return; + } + + triggerSetupComplete(); + } + + function getId() { + return divId; + } + + function getOrtbVideo() { + supportedMediaTypes = supportedMediaTypes || utils.getSupportedMediaTypes(MEDIA_TYPES); + + const video = { + mimes: supportedMediaTypes, + protocols: [ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ], + // h: player.getHeight(), + // w: player.getWidth(), + placement: utils.getPlacement(playerConfig.advertising), + maxextended: -1, // extension is allowed, and there is no time limit imposed. + boxingallowed: 1, + playbackmethod: [utils.getPlaybackMethod(config)], + playbackend: 1, + api: [ + API_FRAMEWORKS.VPAID_2_0, + API_FRAMEWORKS.OMID_1_0 + ], + }; + + return video; + } + + function getOrtbContent() { + } + + function setAdTagUrl(adTagUrl, options) { + setupPlayer(playerConfig, adTagUrl || options.adXml) + } + + function onEvent(externalEventName, callback, basePayload) { + if (externalEventName === SETUP_COMPLETE) { + setupCompleteCallbacks.push(callback); + return; + } + + if (externalEventName === SETUP_FAILED) { + setupFailedCallbacks.push(callback); + return; + } + + let getEventPayload; + + switch (externalEventName) { + case AD_REQUEST: + case AD_PLAY: + case AD_PAUSE: + case AD_LOADED: + case AD_STARTED: + case AD_IMPRESSION: + case AD_CLICK: + case AD_SKIPPED: + case AD_ERROR: + case AD_COMPLETE: + case MUTE: + case VOLUME: + case ERROR: + case PLAYER_RESIZE: + getEventPayload = e => ({ + height: player.getAdHeight(), + width: player.getAdWidth(), + }); + break; + default: + return; + } + + // eslint-disable-next-line no-unreachable + const playerEventName = utils.getPlayerEvent(externalEventName); + const eventHandler = getEventHandler(externalEventName, callback, basePayload, getEventPayload) + player && player.on(playerEventName, eventHandler); + callbackStorage.storeCallback(playerEventName, eventHandler, callback); + } + + function offEvent(event, callback) { + const playerEventName = utils.getPlayerEvent(event); + const eventHandler = callbackStorage.getCallback(playerEventName, callback); + if (eventHandler) { + player && player.off(playerEventName, eventHandler); + } else { + player && player.off(playerEventName); + } + callbackStorage.clearCallback(playerEventName, callback); + } + + function destroy() { + if (!player) { + return; + } + player.remove(); + player = null; + } + + return { + init, + getId, + getOrtbVideo, + getOrtbContent, + setAdTagUrl, + onEvent, + offEvent, + destroy + }; + + function setupPlayer(config, urlOrXml) { + if (!config || player) { + return; + } + const playerConfig = utils.getConfig(config, urlOrXml); + + if (!playerConfig) { + return; + } + + player = adPlayerPro(divId); + callbackStorage.addAllCallbacks(player.on); + player.on('AdStopped', () => player = null); + player.setup(playerConfig); + } + + function triggerSetupComplete() { + if (!setupCompleteCallbacks.length) { + return; + } + + const payload = getSetupCompletePayload(); + setupCompleteCallbacks.forEach(callback => callback(SETUP_COMPLETE, payload)); + setupCompleteCallbacks = []; + } + + function getSetupCompletePayload() { + return { + divId, + playerVersion, + type: SETUP_COMPLETE + }; + } + + function triggerSetupFailure(errorCode, msg, sourceError) { + if (!setupFailedCallbacks.length) { + return; + } + + const payload = { + divId, + playerVersion, + type: SETUP_FAILED, + errorCode: errorCode, + errorMessage: msg, + sourceError: sourceError + }; + + setupFailedCallbacks.forEach(callback => callback(SETUP_FAILED, payload)); + setupFailedCallbacks = []; + } +} + +/** + * @param {Object} config - videoProviderConfig + * @param {sharedUtils} sharedUtils + * @returns {Object} - VideoProvider + */ +const adPlayerProSubmoduleFactory = function (config, sharedUtils) { + const callbackStorage = callbackStorageFactory(); + return AdPlayerProProvider(config, window.playerPro, callbackStorage, utils); +} + +adPlayerProSubmoduleFactory.vendorCode = AD_PLAYER_PRO_VENDOR; +submodule('video', adPlayerProSubmoduleFactory); +export default adPlayerProSubmoduleFactory; + +// HELPERS + +export const utils = { + getConfig: function (config, urlOrXml) { + if (!config || !urlOrXml) { + return; + } + + const params = config.params || {}; + params.placementId = config.placementId; + params.advertising = params.advertising || {}; + params.advertising.tag = params.advertising.tag || {}; + + params._pType = 'pbjs'; + params.advertising.tag.url = urlOrXml; + return params; + }, + + getPlayerEvent: function (eventName) { + switch (eventName) { + case DESTROYED: + return 'AdStopped'; + + case AD_REQUEST: + return 'AdRequest'; + case AD_LOADED: + return 'AdLoaded'; + case AD_STARTED: + return 'AdStarted'; + case AD_IMPRESSION: + return 'AdImpression'; + case AD_PLAY: + return 'AdPlaying'; + case AD_PAUSE: + return 'AdPaused'; + case AD_CLICK: + return 'AdClickThru'; + case AD_SKIPPED: + return 'AdSkipped'; + case AD_ERROR: + return 'AdError'; + case AD_COMPLETE: + return 'AdCompleted'; + case VOLUME: + return 'AdVolumeChange'; + case PLAYER_RESIZE: + return 'AdSizeChange'; + // case FULLSCREEN: + // return FULLSCREEN; + default: + return eventName; + } + }, + + getSupportedMediaTypes: function (mediaTypes = []) { + const el = document.createElement('video'); + return mediaTypes + .filter(mediaType => el.canPlayType(mediaType)) + .concat(VPAID_MIME_TYPE); // Always allow VPAIDs. + }, + + /** + * Determine the ad placement + * @param {Object} adConfig + * @return {PLACEMENT|undefined} + */ + getPlacement: function (adConfig) { + adConfig = adConfig || {}; + + switch (adConfig.type) { + case 'inPage': + return PLACEMENT.ARTICLE; + case 'rewarded': + case 'inView': + return PLACEMENT.INTERSTITIAL_SLIDER_FLOATING; + default: + return PLACEMENT.BANNER; + } + }, + + getPlaybackMethod: function ({autoplay, mute}) { + if (autoplay) { + return mute ? PLAYBACK_METHODS.AUTOPLAY_MUTED : PLAYBACK_METHODS.AUTOPLAY; + } + return PLAYBACK_METHODS.CLICK_TO_PLAY; + } +} + +/** + * Tracks which functions are attached to events + * @typedef CallbackStorage + * @function storeCallback + * @function getCallback + * @function clearCallback + * @function addAllCallbacks + * @function clearStorage + */ + +/** + * @returns {CallbackStorage} + */ +export function callbackStorageFactory() { + let storage = {}; + let storageHandlers = {}; + + function storeCallback(eventType, eventHandler, callback) { + let eventHandlers = storage[eventType]; + if (!eventHandlers) { + eventHandlers = storage[eventType] = {}; + } + + eventHandlers[callback] = eventHandler; + addHandler(eventType, eventHandler); + } + + function getCallback(eventType, callback) { + let eventHandlers = storage[eventType]; + if (eventHandlers) { + return eventHandlers[callback]; + } + } + + function clearCallback(eventType, callback) { + if (!callback) { + delete storage[eventType]; + delete storageHandlers[eventType]; + return; + } + let eventHandlers = storage[eventType]; + if (eventHandlers) { + const eventHandler = eventHandlers[callback]; + if (eventHandler) { + delete eventHandlers[callback]; + clearHandler(eventType, eventHandler); + } + } + } + + function clearStorage() { + storage = {}; + storageHandlers = {}; + } + + function addHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + if (!eventHandlers) { + eventHandlers = storageHandlers[eventType] = []; + } + eventHandlers.push(eventHandler); + } + + function clearHandler(eventType, eventHandler) { + let eventHandlers = storageHandlers[eventType]; + eventHandlers = eventHandlers.filter(handler => handler !== eventHandler); + if (eventHandlers.length) { + storageHandlers[eventType] = eventHandlers; + } else { + delete storageHandlers[eventType]; + } + } + + function addAllCallbacks(functionOnPlayer) { + for (let eventType in storageHandlers) { + storageHandlers[eventType].forEach(handler => functionOnPlayer(eventType, handler)); + } + } + + return { + storeCallback, + getCallback, + clearCallback, + addAllCallbacks, + clearStorage, + } +} diff --git a/modules/adplayerproVideoProvider.md b/modules/adplayerproVideoProvider.md new file mode 100644 index 00000000000..d0b0601afeb --- /dev/null +++ b/modules/adplayerproVideoProvider.md @@ -0,0 +1,54 @@ +# Overview + +Module Name: AdPlayer.Pro Video Provider +Module Type: Video Submodule +Video Player: AdPlayer.Pro +Player website: https://adplayer.pro +Maintainer: support@adplayer.pro + +# Description + +Video provider to connect the Prebid Video Module to AdPlayer.Pro. + +# Requirements + +Your page must embed a build of AdPlayer.Pro. +i.e. +```html + + + +``` + +# Configuration + +The AdPlayer.Pro Video Provider requires the following configuration: + +```javascript +pbjs.setConfig({ + video: { + providers: [{ + divId: 'player', // required, this is the id of the div element where the player will be placed + vendorCode: 3, // AdPlayer.Pro vendorCode + playerConfig: { + placementId: 'c9gebfehcqjE', // required, this placementId is only for demo purposes + params: { + 'type': 'inView', + 'muted': true, + 'autoStart': true, + 'advertising': { + 'controls': true, + 'closeButton': true, + }, + 'width': '600', + 'height': '300' + } + }, + }] + } +}); +``` + +[Additional embed instructions](https://docs.adplayer.pro) + +[Obtaining a license](https://adplayer.pro/contacts) diff --git a/modules/adpod.js b/modules/adpod.js index b6d13673178..35a78766979 100644 --- a/modules/adpod.js +++ b/modules/adpod.js @@ -451,7 +451,6 @@ export function callPrebidCacheAfterAuction(bids, callback) { /** * Compare function to be used in sorting long-form bids. This will compare bids on price per second. * @param {Object} bid - * @param {Object} bid */ export function sortByPricePerSecond(a, b) { if (a.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / a.video.durationBucket < b.adserverTargeting[TARGETING_KEYS.PRICE_BUCKET] / b.video.durationBucket) { @@ -465,10 +464,10 @@ export function sortByPricePerSecond(a, b) { /** * This function returns targeting keyvalue pairs for long-form adserver modules. Freewheel and GAM are currently supporting Prebid long-form - * @param {Object} options - * @param {Array[string]} codes - * @param {function} callback - * @returns targeting kvs for adUnitCodes + * @param {Object} options - Options for targeting. + * @param {Array} options.codes - Array of ad unit codes. + * @param {function} options.callback - Callback function to handle the targeting key-value pairs. + * @returns {Object} Targeting key-value pairs for ad unit codes. */ export function getTargeting({ codes, callback } = {}) { if (!callback) { diff --git a/modules/adprimeBidAdapter.js b/modules/adprimeBidAdapter.js index 55ee1f0900c..e40d20356af 100644 --- a/modules/adprimeBidAdapter.js +++ b/modules/adprimeBidAdapter.js @@ -1,183 +1,46 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'adprime'; const AD_URL = 'https://delta.adprime.com/pbjs'; const SYNC_URL = 'https://sync.adprime.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } - 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; + if (bid.userId && bid.userId.idl_env) { + placement.identeties = {}; + placement.identeties.identityLink = bid.userId.idl_env; } -} -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } + placement.keywords = getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords); + placement.audiences = bid.params.audiences || []; +}; - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - deviceWidth: winTop.screen.width, - deviceHeight: winTop.screen.height, - language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - secure: 1, - host: location.host, - page: location.pathname, - placements: placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - const { mediaTypes } = bid; - const placement = {}; - let sizes - let identeties = {} - 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]; - } - } - if (bid.userId && bid.userId.idl_env) { - identeties.identityLink = bid.userId.idl_env - } - - placements.push({ - ...placement, - placementId: bid.params.placementId, - bidId: bid.bidId, - sizes: sizes || [], - wPlayer: sizes ? sizes[0] : 0, - hPlayer: sizes ? sizes[1] : 0, - schain: bid.schain || {}, - keywords: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords), - audiences: bid.params.audiences || [], - identeties, - bidFloor: getBidFloor(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 - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/adriverBidAdapter.js b/modules/adriverBidAdapter.js index 5bce315f572..7ac8e4fb728 100644 --- a/modules/adriverBidAdapter.js +++ b/modules/adriverBidAdapter.js @@ -1,5 +1,5 @@ // ADRIVER BID ADAPTER for Prebid 1.13 -import {logInfo, getWindowLocation, _each, getBidIdParameter} from '../src/utils.js'; +import {logInfo, getWindowLocation, _each, getBidIdParameter, isPlainObject} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -188,12 +188,12 @@ function _getFloor(bid, currencyPar, sizes) { size: isSize ? sizes : '*' }); - if (typeof floorInfo === 'object' && - !isNaN(parseFloat(floorInfo.floor))) { + if (isPlainObject(floorInfo) && + !isNaN(parseFloat(floorInfo?.floor))) { floor = floorInfo.floor; } - if (typeof floorInfo === 'object' && floorInfo.currency) { + if (isPlainObject(floorInfo) && floorInfo.currency) { currencyResult = floorInfo.currency; } } diff --git a/modules/ads_interactiveBidAdapter.js b/modules/ads_interactiveBidAdapter.js new file mode 100644 index 00000000000..9a827cb4914 --- /dev/null +++ b/modules/ads_interactiveBidAdapter.js @@ -0,0 +1,21 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'ads_interactive'; +const AD_URL = 'https://bntb.adsinteractive.com/pbjs'; +const SYNC_URL = 'https://cstb.adsinteractive.com'; +const GVLID = 1212; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/ads_interactiveBidAdapter.md b/modules/ads_interactiveBidAdapter.md new file mode 100755 index 00000000000..43c4727db45 --- /dev/null +++ b/modules/ads_interactiveBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: AdsInteractive Bidder Adapter +Module Type: AdsInteractive Bidder Adapter +Maintainer: it@adsinteractive.com +``` + +# Description + +Connects to AdsInteractive exchange for bids. +AdsInteractive 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: 'ads_interactive', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'ads_interactive', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'ads_interactive', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/adtargetBidAdapter.js b/modules/adtargetBidAdapter.js index a1dec5a420f..0faf740ac54 100644 --- a/modules/adtargetBidAdapter.js +++ b/modules/adtargetBidAdapter.js @@ -12,6 +12,7 @@ const syncsCache = {}; export const spec = { code: BIDDER_CODE, + gvlid: 779, supportedMediaTypes: [VIDEO, BANNER], isBidRequestValid: function (bid) { return !!deepAccess(bid, 'params.aid'); diff --git a/modules/adtelligentBidAdapter.js b/modules/adtelligentBidAdapter.js index a95b9ed5652..6d4c0b6ed6a 100644 --- a/modules/adtelligentBidAdapter.js +++ b/modules/adtelligentBidAdapter.js @@ -4,7 +4,6 @@ import {ADPOD, BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {Renderer} from '../src/Renderer.js'; import {find} from '../src/polyfill.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -26,7 +25,6 @@ const HOST_GETTERS = { janet: () => 'ghb.bidder.jmgads.com', ocm: () => 'ghb.cenarius.orangeclickmedia.com', '9dotsmedia': () => 'ghb.platform.audiodots.com', - copper6: () => 'ghb.app.copper6.com', indicue: () => 'ghb.console.indicue.com', } const getUri = function (bidderCode) { @@ -49,7 +47,6 @@ export const spec = { { code: 'selectmedia', gvlid: 775 }, { code: 'ocm', gvlid: 1148 }, '9dotsmedia', - 'copper6', 'indicue', ], supportedMediaTypes: [VIDEO, BANNER], @@ -138,11 +135,6 @@ export const spec = { return bids; }, - transformBidParams(params) { - return convertTypes({ - 'aid': 'number', - }, params); - } }; function parseRTBResponse(serverResponse, adapterRequest) { diff --git a/modules/adtrgtmeBidAdapter.js b/modules/adtrgtmeBidAdapter.js index 4dc95ce6bc1..18cbd1e6ae7 100644 --- a/modules/adtrgtmeBidAdapter.js +++ b/modules/adtrgtmeBidAdapter.js @@ -2,7 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'adtrgtme'; @@ -55,7 +55,7 @@ function extractUserSyncUrls(syncOptions, pixels) { } function isSecure(bid) { - return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0; + return deepAccess(bid, 'params.bidOverride.imp.secure') ?? deepAccess(bid, 'ortb2Imp.secure') ?? 1; }; function getMediaType(bid) { @@ -90,7 +90,7 @@ function getFloorModuleData(bid) { mediaType: BANNER, size: '*' }; - return (isFn(bid.getFloor)) ? bid.getFloor(getFloorRequestObject) : false; + return (isFn(bid.getFloor)) ? (bid.getFloor(getFloorRequestObject) || {}) : false; }; function generateOpenRtbObject(bidderRequest, bid) { diff --git a/modules/adtrueBidAdapter.js b/modules/adtrueBidAdapter.js index 389986eb586..a6186d6129f 100644 --- a/modules/adtrueBidAdapter.js +++ b/modules/adtrueBidAdapter.js @@ -43,6 +43,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER }; diff --git a/modules/advRedAnalyticsAdapter.js b/modules/advRedAnalyticsAdapter.js new file mode 100644 index 00000000000..8ad30ed351d --- /dev/null +++ b/modules/advRedAnalyticsAdapter.js @@ -0,0 +1,198 @@ +import {generateUUID, logInfo} from '../src/utils.js' +import {ajaxBuilder} from '../src/ajax.js' +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' +import adapterManager from '../src/adapterManager.js' +import {EVENTS} from '../src/constants.js' +import {getRefererInfo} from '../src/refererDetection.js'; + +/** + * advRedAnalyticsAdapter.js - analytics adapter for AdvRed + */ +const DEFAULT_EVENT_URL = 'https://api.adv.red/api/event' + +let ajax = ajaxBuilder(10000) +let pwId +let initOptions +let flushInterval +let queue = [] + +let advRedAnalytics = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType: 'endpoint'}), { + track({eventType, args}) { + handleEvent(eventType, args) + } +}) + +function sendEvents() { + if (queue.length > 0) { + const message = { + pwId: pwId, + publisherId: initOptions.publisherId, + events: queue, + pageUrl: getRefererInfo().page + } + queue = [] + + const url = initOptions.url ? initOptions.url : DEFAULT_EVENT_URL + ajax( + url, + () => logInfo('AdvRed Analytics sent ' + queue.length + ' events'), + JSON.stringify(message), + { + method: 'POST', + contentType: 'application/json', + withCredentials: true + } + ) + } +} + +function convertAdUnit(adUnit) { + if (!adUnit) return adUnit + + const shortAdUnit = {} + shortAdUnit.code = adUnit.code + shortAdUnit.sizes = adUnit.sizes + return shortAdUnit +} + +function convertBid(bid) { + if (!bid) return bid + + const shortBid = {} + shortBid.adUnitCode = bid.adUnitCode + shortBid.bidder = bid.bidder + shortBid.cpm = bid.cpm + shortBid.currency = bid.currency + shortBid.mediaTypes = bid.mediaTypes + shortBid.sizes = bid.sizes + shortBid.serverResponseTimeMs = bid.serverResponseTimeMs + return shortBid +} + +function convertAuctionInit(origEvent) { + let shortEvent = {} + shortEvent.auctionId = origEvent.auctionId + shortEvent.timeout = origEvent.timeout + shortEvent.adUnits = origEvent.adUnits && origEvent.adUnits.map(convertAdUnit) + return shortEvent +} + +function convertBidRequested(origEvent) { + let shortEvent = {} + shortEvent.bidderCode = origEvent.bidderCode + shortEvent.bids = origEvent.bids && origEvent.bids.map(convertBid) + shortEvent.timeout = origEvent.timeout + return shortEvent +} + +function convertBidTimeout(origEvent) { + let shortEvent = {} + shortEvent.bids = origEvent && origEvent.map ? origEvent.map(convertBid) : origEvent + return shortEvent +} + +function convertBidderError(origEvent) { + let shortEvent = {} + shortEvent.bids = origEvent.bidderRequest && origEvent.bidderRequest.bids && origEvent.bidderRequest.bids.map(convertBid) + return shortEvent +} + +function convertAuctionEnd(origEvent) { + let shortEvent = {} + shortEvent.adUnitCodes = origEvent.adUnitCodes + shortEvent.bidsReceived = origEvent.bidsReceived && origEvent.bidsReceived.map(convertBid) + shortEvent.noBids = origEvent.noBids && origEvent.noBids.map(convertBid) + return shortEvent +} + +function convertBidWon(origEvent) { + let shortEvent = {} + shortEvent.adUnitCode = origEvent.adUnitCode + shortEvent.bidderCode = origEvent.bidderCode + shortEvent.mediaType = origEvent.mediaType + shortEvent.netRevenue = origEvent.netRevenue + shortEvent.cpm = origEvent.cpm + shortEvent.size = origEvent.size + shortEvent.currency = origEvent.currency + return shortEvent +} + +function handleEvent(eventType, origEvent) { + try { + origEvent = origEvent ? JSON.parse(JSON.stringify(origEvent)) : {} + } catch (e) { + } + + let shortEvent + switch (eventType) { + case EVENTS.AUCTION_INIT: { + shortEvent = convertAuctionInit(origEvent) + break + } + case EVENTS.BID_REQUESTED: { + shortEvent = convertBidRequested(origEvent) + break + } + case EVENTS.BID_TIMEOUT: { + shortEvent = convertBidTimeout(origEvent) + break + } + case EVENTS.BIDDER_ERROR: { + shortEvent = convertBidderError(origEvent) + break + } + case EVENTS.AUCTION_END: { + shortEvent = convertAuctionEnd(origEvent) + break + } + case EVENTS.BID_WON: { + shortEvent = convertBidWon(origEvent) + break + } + default: + return + } + + shortEvent.eventType = eventType + shortEvent.auctionId = origEvent.auctionId + shortEvent.timestamp = origEvent.timestamp || Date.now() + + sendEvent(shortEvent) +} + +function sendEvent(event) { + queue.push(event) + + if (event.eventType === EVENTS.AUCTION_END) { + sendEvents() + } +} + +advRedAnalytics.originEnableAnalytics = advRedAnalytics.enableAnalytics +advRedAnalytics.enableAnalytics = function (config) { + initOptions = config.options || {} + pwId = generateUUID() + flushInterval = setInterval(sendEvents, 1000) + + advRedAnalytics.originEnableAnalytics(config) +} + +advRedAnalytics.originDisableAnalytics = advRedAnalytics.disableAnalytics +advRedAnalytics.disableAnalytics = function () { + clearInterval(flushInterval) + sendEvents() + advRedAnalytics.originDisableAnalytics() +} + +adapterManager.registerAnalyticsAdapter({ + adapter: advRedAnalytics, + code: 'advRed' +}) + +advRedAnalytics.getOptions = function () { + return initOptions +} + +advRedAnalytics.sendEvents = sendEvents + +export default advRedAnalytics diff --git a/modules/advRedAnalyticsAdapter.md b/modules/advRedAnalyticsAdapter.md new file mode 100644 index 00000000000..59345dfd01e --- /dev/null +++ b/modules/advRedAnalyticsAdapter.md @@ -0,0 +1,30 @@ +# Overview +``` +Module Name: AdvRed Analytics Adapter +Module Type: Analytics Adapter +Maintainer: support@adv.red +``` + +### Usage + +The AdvRed analytics adapter can be used by all clients after approval. For more information, +please visit + +### Analytics Options +| Param enableAnalytics | Scope | Type | Description | Example | +|-----------------------|----------|--------|------------------------------------------------------|----------------------------------------| +| provider | Required | String | The name of this Adapter. | `'advRed'` | +| params | Required | Object | Details of module params. | | +| params.publisherId | Required | String | This is the Publisher ID value obtained from AdvRed. | `'123456'` | +| params.url | Optional | String | Custom URL of the endpoint to collect the events | `'https://pub1.api.adv.red/api/event'` | + +### Example Configuration + +```javascript +pbjs.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '123456' // change to the Publisher ID you received from AdvRed + } +}); +``` diff --git a/modules/advangelistsBidAdapter.js b/modules/advangelistsBidAdapter.js index a916e07a963..3a571831505 100755 --- a/modules/advangelistsBidAdapter.js +++ b/modules/advangelistsBidAdapter.js @@ -1,25 +1,23 @@ -import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; +import { isEmpty } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { createRequestData, getBannerBidFloor, getBannerBidParam, getBannerSizes, getVideoBidFloor, getVideoBidParam, getVideoSizes, isBannerBidValid, isVideoBid, isVideoBidValid } from '../libraries/advangUtils/index.js'; const ADAPTER_VERSION = '1.0'; const BIDDER_CODE = 'advangelists'; - -export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; -export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid=';// 0cf8d6d643e13d86a5b6374148a4afac'; -export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; -export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; +export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; +export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; let pubid = ''; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO], - + aliases: ['saambaa'], isBidRequestValid(bidRequest) { - if (typeof bidRequest != 'undefined') { + 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; @@ -35,7 +33,7 @@ export const spec = { requests.push({ method: 'POST', url: VIDEO_ENDPOINT + pubid, - data: createVideoRequestData(bid, bidderRequest), + data: createRequestData(bid, bidderRequest, true, getVideoBidParam, getVideoSizes, getVideoBidFloor), bidRequest: bid }); }); @@ -45,16 +43,16 @@ export const spec = { requests.push({ method: 'POST', url: BANNER_ENDPOINT + pubid, - data: createBannerRequestData(bid, bidderRequest), + data: createRequestData(bid, bidderRequest, false, getBannerBidParam, getBannerSizes, getBannerBidFloor, BIDDER_CODE, ADAPTER_VERSION), bidRequest: bid }); }); return requests; }, - interpretResponse(serverResponse, {bidRequest}) { + interpretResponse(serverResponse, { bidRequest }) { let response = serverResponse.body; - if (response !== null && isEmpty(response) == false) { + if (response !== null && isEmpty(response) === false) { if (isVideoBid(bidRequest)) { let bidResponse = { requestId: response.id, @@ -63,11 +61,11 @@ export const spec = { height: response.seatbid[0].bid[0].h, ttl: response.seatbid[0].bid[0].ttl || 60, creativeId: response.seatbid[0].bid[0].crid, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, currency: response.cur, + meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, mediaType: VIDEO, netRevenue: true - } + }; if (response.seatbid[0].bid[0].adm) { bidResponse.vastXml = response.seatbid[0].bid[0].adm; @@ -93,298 +91,10 @@ export const spec = { meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, mediaType: BANNER, netRevenue: true - } + }; } } } }; -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - -function isVideoBidValid(bid) { - return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); -} - -function isBannerBidValid(bid) { - return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); -} - -function getVideoBidParam(bid, key) { - return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); -} - -function getBannerBidParam(bid, key) { - return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function findAndFillParam(o, key, value) { - try { - if (typeof value === 'function') { - o[key] = value(); - } else { - o[key] = value; - } - } catch (ex) {} -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getTopWindowReferrer(bidderRequest) { - return bidderRequest?.refererInfo?.ref || ''; -} - -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - -function createVideoRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(bidderRequest); - - let sizes = getVideoSizes(bid); - let firstSize = getFirstSize(sizes); - let bidfloor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 2 : getVideoBidFloor(bid); - let video = getVideoTargetingParams(bid); - const o = { - 'device': { - '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': Math.min(3000, bidderRequest.timeout), - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getVideoBidParam(bid, 'placement'); - - for (let j = 0; j < sizes.length; j++) { - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': bidfloor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'video': Object.assign({ - 'id': 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) { - return parseUrl(bidderRequest?.refererInfo?.page, {decodeSearchAsString: true}); -} - -function createBannerRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(bidderRequest); - - let sizes = getBannerSizes(bid); - let bidfloor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 2 : getBannerBidFloor(bid); - - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1 - }, - 'at': 2, - 'site': {}, - 'tmax': Math.min(3000, bidderRequest.timeout), - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getBannerBidParam(bid, 'placement'); - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': bidfloor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'banner': { - 'id': 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/adverxoBidAdapter.js b/modules/adverxoBidAdapter.js new file mode 100644 index 00000000000..f293d7e01a6 --- /dev/null +++ b/modules/adverxoBidAdapter.js @@ -0,0 +1,356 @@ +import * as utils from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO, NATIVE} from '../src/mediaTypes.js'; +import {ortbConverter as OrtbConverter} from '../libraries/ortbConverter/converter.js'; +import {Renderer} from '../src/Renderer.js'; +import {deepAccess, deepSetValue} from '../src/utils.js'; +import {config} from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + * @typedef {import('../src/auction.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync + */ + +const BIDDER_CODE = 'adverxo'; + +const ALIASES = [ + {code: 'adport', skipPbsAliasing: true}, + {code: 'bidsmind', skipPbsAliasing: true}, + {code: 'mobupps', skipPbsAliasing: true} +]; + +const AUCTION_URLS = { + adverxo: 'js.pbsadverxo.com', + adport: 'diclotrans.com', + bidsmind: 'egrevirda.com', + mobupps: 'traffhb.com' +}; + +const ENDPOINT_URL_AD_UNIT_PLACEHOLDER = '{AD_UNIT}'; +const ENDPOINT_URL_AUTH_PLACEHOLDER = '{AUTH}'; +const ENDPOINT_URL_HOST_PLACEHOLDER = '{HOST}'; + +const ENDPOINT_URL = `https://${ENDPOINT_URL_HOST_PLACEHOLDER}/pickpbs?id=${ENDPOINT_URL_AD_UNIT_PLACEHOLDER}&auth=${ENDPOINT_URL_AUTH_PLACEHOLDER}`; + +const ORTB_MTYPES = { + 1: BANNER, + 2: VIDEO, + 4: NATIVE +}; + +const USYNC_TYPES = { + IFRAME: 'iframe', + REDIRECT: 'image' +}; + +const DEFAULT_CURRENCY = 'USD'; + +const ortbConverter = OrtbConverter({ + context: { + netRevenue: true, + ttl: 60, + }, + request: function request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + + utils.deepSetValue(request, 'device.ip', 'caller'); + utils.deepSetValue(request, 'ext.avx_add_vast_url', 1); + + const eids = deepAccess(bidderRequest, 'bids.0.userIdAsEids'); + + if (eids && eids.length) { + deepSetValue(request, 'user.ext.eids', eids); + } + + return request; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floor = adverxoUtils.getBidFloor(bidRequest); + + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } + + return imp; + }, + bidResponse: function (buildBidResponse, bid, context) { + bid.adm = bid.adm.replaceAll(`\${AUCTION_PRICE}`, bid.price); + + if (FEATURES.NATIVE && ORTB_MTYPES[bid.mtype] === NATIVE) { + if (typeof bid?.adm === 'string') { + bid.adm = JSON.parse(bid.adm); + } + + if (bid?.adm?.native) { + bid.adm = bid.adm.native; + } + } + + const result = buildBidResponse(bid, context); + + if (FEATURES.VIDEO) { + if (bid?.ext?.avx_vast_url) { + result.vastUrl = bid.ext.avx_vast_url; + } + + if (bid?.ext?.avx_video_renderer_url) { + result.avxVideoRendererUrl = bid.ext.avx_video_renderer_url; + } + } + + return result; + } +}); + +const userSyncUtils = { + buildUsyncParams: function (gdprConsent, uspConsent, gppConsent) { + const params = []; + + if (gdprConsent) { + params.push('gdpr=' + (gdprConsent.gdprApplies ? 1 : 0)); + params.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + + if (config.getConfig('coppa') === true) { + params.push('coppa=1'); + } + + if (uspConsent) { + params.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + params.push('gpp=' + encodeURIComponent(gppConsent.gppString)); + params.push('gpp_sid=' + encodeURIComponent(gppConsent?.applicableSections?.join(','))); + } + + return params.length ? params.join('&') : ''; + } +}; + +const videoUtils = { + createOutstreamVideoRenderer: function (bid) { + const renderer = Renderer.install({ + id: bid.bidId, + url: bid.avxVideoRendererUrl, + loaded: false, + adUnitCode: bid.adUnitCode + }); + + try { + renderer.setRender(this.outstreamRender.bind(this)); + } catch (err) { + utils.logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; + }, + + outstreamRender: function (bid, doc) { + bid.renderer.push(() => { + const win = (doc) ? doc.defaultView : window; + + win.adxVideoRenderer.renderAd({ + targetId: bid.adUnitCode, + adResponse: {content: bid.vastXml} + }); + }); + } +}; + +const adverxoUtils = { + buildAuctionUrl: function (bidderCode, host, adUnitId, adUnitAuth) { + const auctionUrl = host || AUCTION_URLS[bidderCode]; + + return ENDPOINT_URL + .replace(ENDPOINT_URL_HOST_PLACEHOLDER, auctionUrl) + .replace(ENDPOINT_URL_AD_UNIT_PLACEHOLDER, adUnitId) + .replace(ENDPOINT_URL_AUTH_PLACEHOLDER, adUnitAuth); + }, + + groupBidRequestsByAdUnit: function (bidRequests) { + const groupedBidRequests = new Map(); + + bidRequests.forEach(bidRequest => { + const adUnit = { + host: bidRequest.params.host, + id: bidRequest.params.adUnitId, + auth: bidRequest.params.auth, + }; + + if (!groupedBidRequests.get(adUnit)) { + groupedBidRequests.set(adUnit, []); + } + + groupedBidRequests.get(adUnit).push(bidRequest); + }); + + return groupedBidRequests; + }, + + getBidFloor: function (bid) { + if (utils.isFn(bid.getFloor)) { + const floor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType: '*', + size: '*', + }); + + if (utils.isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) { + return floor.floor; + } + } + + return null; + } +}; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, NATIVE, VIDEO], + aliases: ALIASES, + + /** + * Determines whether 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.isPlainObject(bid.params) || !Object.keys(bid.params).length) { + utils.logWarn('Adverxo Bid Adapter: bid params must be provided.'); + return false; + } + + if (!bid.params.adUnitId || typeof bid.params.adUnitId !== 'number') { + utils.logWarn('Adverxo Bid Adapter: adUnitId bid param is required and must be a number'); + return false; + } + + if (!bid.params.auth || typeof bid.params.auth !== 'string') { + utils.logWarn('Adverxo Bid Adapter: auth bid param is required and must be a string'); + return false; + } + + return true; + }, + + /** + * Make a server request from the list of BidRequests. + * + * @param {BidRequest[]} validBidRequests an array of bids + * @param {BidderRequest} bidderRequest an array of bids + * @return {ServerRequest} Info describing the request to the server. + */ + buildRequests: function (validBidRequests, bidderRequest) { + const result = []; + + const bidRequestsByAdUnit = adverxoUtils.groupBidRequestsByAdUnit(validBidRequests); + + bidRequestsByAdUnit.forEach((adUnitBidRequests, adUnit) => { + const ortbRequest = ortbConverter.toORTB({ + bidRequests: adUnitBidRequests, + bidderRequest + }); + + result.push({ + method: 'POST', + url: adverxoUtils.buildAuctionUrl(bidderRequest.bidderCode, adUnit.host, adUnit.id, adUnit.auth), + data: ortbRequest, + bids: adUnitBidRequests + }); + }); + + return result; + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @param {BidRequest} bidRequest Adverxo bidRequest + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse || !bidRequest) { + return []; + } + + const bids = ortbConverter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids.map((bid) => { + const thisRequest = utils.getBidRequest(bid.requestId, [bidRequest]); + const context = utils.deepAccess(thisRequest, 'mediaTypes.video.context'); + + if (FEATURES.VIDEO && bid.mediaType === 'video' && context === 'outstream') { + bid.renderer = videoUtils.createOutstreamVideoRenderer(bid); + } + + return bid; + }); + }, + + /** + * Register the user sync pixels which should be dropped after the auction. + * + * @param {SyncOptions} syncOptions Which user syncs are allowed? + * @param {ServerResponse[]} responses List of server's responses. + * @param {*} gdprConsent + * @param {*} uspConsent + * @param {*} gppConsent + * @return {UserSync[]} The user syncs which should be dropped. + */ + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { + if (!responses || responses.length === 0 || (!syncOptions.pixelEnabled && !syncOptions.iframeEnabled)) { + return []; + } + + const privacyParams = userSyncUtils.buildUsyncParams(gdprConsent, uspConsent, gppConsent); + const syncType = syncOptions.iframeEnabled ? USYNC_TYPES.IFRAME : USYNC_TYPES.REDIRECT; + + const result = []; + + for (const response of responses) { + const syncUrls = response.body?.ext?.avx_usync; + + if (!syncUrls || syncUrls.length === 0) { + continue; + } + + for (const url of syncUrls) { + let finalUrl = url; + + if (!finalUrl.includes('?')) { + finalUrl += '?'; + } else { + finalUrl += '&'; + } + + finalUrl += 'type=' + syncType; + + if (privacyParams.length !== 0) { + finalUrl += `&${privacyParams}`; + } + + result.push({ + type: syncType, + url: finalUrl + }); + } + } + + return result; + } +} + +registerBidder(spec); diff --git a/modules/adverxoBidAdapter.md b/modules/adverxoBidAdapter.md new file mode 100644 index 00000000000..ae6072d2738 --- /dev/null +++ b/modules/adverxoBidAdapter.md @@ -0,0 +1,101 @@ +# Overview + +``` +Module Name: Adverxo Bidder Adapter +Module Type: Bidder Adapter +Maintainer: developer@adverxo.com +``` + +# Description + +Module that connects to Adverxo to fetch bids. +Banner, native and video formats are supported. + +# Bid Parameters + +| Name | Required? | Description | Example | Type | +|------------|-----------|-------------------------------------------------------------------|----------------------------------------------|-----------| +| `host` | No | Ad network host. | `prebidTest.adverxo.com` | `String` | +| `adUnitId` | Yes | Unique identifier for the ad unit in Adverxo platform. | `413` | `Integer` | +| `auth` | Yes | Authentication token provided by Adverxo platform for the AdUnit. | `'61336d75e414c77c367ce5c47c2599ce80a8x32b'` | `String` | + +# Test Parameters + +```javascript +var adUnits = [ + { + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [ + [400, 300], + [320, 50] + ] + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 1, + auth: '61336e753414c77c367deac47c2595ce80a8032b' + } + }] + }, + { + code: 'native-ad-div', + mediaTypes: { + native: { + image: { + required: true, + sizes: [400, 300] + }, + title: { + required: true, + len: 75 + }, + body: { + required: false, + len: 200 + }, + cta: { + required: false, + len: 75 + }, + sponsoredBy: { + required: false + } + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 2, + auth: '9a640dfccc3381e71fxc29ffd4a72wabd29g9d86' + } + }] + }, + { + code: 'video-div', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream', + mimes: ['video/mp4'], + maxduration: 30, + skip: 1 + } + }, + bids: [{ + bidder: 'adverxo', + params: { + host: 'example.com', + adUnitId: 3, + auth: '1ax23d9621f21da28a2eab6f79bd5fbcf4d037c1' + } + }] + } +]; + +``` diff --git a/modules/adxcgAnalyticsAdapter.js b/modules/adxcgAnalyticsAdapter.js index 7ad95121209..7538e2962cc 100644 --- a/modules/adxcgAnalyticsAdapter.js +++ b/modules/adxcgAnalyticsAdapter.js @@ -3,7 +3,6 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; /** * Analytics adapter from adxcg.com @@ -123,7 +122,7 @@ function send (data) { ats: adxcgAnalyticsAdapter.context.auctionTimestamp, aav: adxcgAnalyticsVersion, iob: intersectionObserverAvailable(window) ? '1' : '0', - pbv: getGlobal().version, + pbv: '$prebid.version$', sz: window.screen.width + 'x' + window.screen.height } }); diff --git a/modules/adxcgBidAdapter.js b/modules/adxcgBidAdapter.js index dda88575ff5..a0e99572809 100644 --- a/modules/adxcgBidAdapter.js +++ b/modules/adxcgBidAdapter.js @@ -1,6 +1,5 @@ // jshint esversion: 6, es3: false, node: true import { ortbConverter } from '../libraries/ortbConverter/converter.js'; -import { convertTypes } from '../libraries/transformParamsUtils/convertTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { @@ -17,7 +16,7 @@ const BIDDER_CODE = 'adxcg'; const SECURE_BID_URL = 'https://pbc.adxcg.net/rtb/ortb/pbc?adExchangeId=1'; const DEFAULT_CURRENCY = 'EUR'; -const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'battr', 'deals']; +const KNOWN_PARAMS = ['battr', 'deals']; const DEFAULT_TMAX = 500; /** @@ -88,14 +87,6 @@ export const spec = { if (bid.nurl) { triggerPixel(replaceAuctionPrice(bid.nurl, bid.originalCpm)) } - }, - transformBidParams: function (params) { - return convertTypes({ - 'cf': 'string', - 'cp': 'number', - 'ct': 'number', - 'adzoneid': 'string' - }, params); } }; @@ -131,7 +122,7 @@ const converter = ortbConverter({ }; } - imp.secure = Number(window.location.protocol === 'https:'); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; diff --git a/modules/adyoulikeBidAdapter.js b/modules/adyoulikeBidAdapter.js index 146e1d3b24a..fce5d1ae000 100644 --- a/modules/adyoulikeBidAdapter.js +++ b/modules/adyoulikeBidAdapter.js @@ -255,7 +255,7 @@ function getFloor(bidRequest, size, mediaType) { size: [ size.width, size.height ] }); - if (!isNaN(bidFloors.floor) && (bidFloors.currency === CURRENCY)) { + if (!isNaN(bidFloors?.floor) && (bidFloors?.currency === CURRENCY)) { return bidFloors.floor; } } diff --git a/modules/aidemBidAdapter.js b/modules/aidemBidAdapter.js index c6a5cd96fb6..07aee262baa 100644 --- a/modules/aidemBidAdapter.js +++ b/modules/aidemBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; +import {deepAccess, deepClone, deepSetValue, isBoolean, isNumber, isStr, logError, logInfo} from '../src/utils.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -10,6 +10,7 @@ const BIDDER_CODE = 'aidem'; const BASE_URL = 'https://zero.aidemsrv.com'; const LOCAL_BASE_URL = 'http://127.0.0.1:8787'; +const GVLID = 1218 const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; const REQUIRED_VIDEO_PARAMS = [ 'mimes', 'protocols', 'context' ]; @@ -59,7 +60,7 @@ const converter = ortbConverter({ const request = buildRequest(imps, bidderRequest, context); deepSetValue(request, 'at', 1); setPrebidRequestEnvironment(request); - deepSetValue(request, 'regs', getRegs()); + deepSetValue(request, 'regs', getRegs(bidderRequest)); deepSetValue(request, 'site.publisher.id', bidderRequest.bids[0].params.publisherId); deepSetValue(request, 'site.id', bidderRequest.bids[0].params.siteId); return request; @@ -106,22 +107,22 @@ function recur(obj) { return result; } -function getRegs() { +function getRegs(bidderRequest) { let regs = {}; - const consentManagement = config.getConfig('consentManagement'); + const euConsentManagement = bidderRequest.gdprConsent; + const usConsentManagement = bidderRequest.uspConsent; const coppa = config.getConfig('coppa'); - if (consentManagement && !!(consentManagement.gdpr)) { - deepSetValue(regs, 'gdpr_applies', !!consentManagement.gdpr); + if (euConsentManagement && euConsentManagement.consentString) { + deepSetValue(regs, 'gdpr_applies', !!euConsentManagement.consentString); } else { deepSetValue(regs, 'gdpr_applies', false); } - if (consentManagement && deepAccess(consentManagement, 'usp.cmpApi') === 'static') { - deepSetValue(regs, 'usp_applies', !!deepAccess(consentManagement, 'usp')); - deepSetValue(regs, 'us_privacy', deepAccess(consentManagement, 'usp.consentData.getUSPData.uspString')); + if (usConsentManagement) { + deepSetValue(regs, 'usp_applies', true); + deepSetValue(regs, 'us_privacy', bidderRequest.uspConsent); } else { deepSetValue(regs, 'usp_applies', false); } - if (isBoolean(coppa)) { deepSetValue(regs, 'coppa_applies', !!coppa); } else { @@ -132,7 +133,7 @@ function getRegs() { } function setPrebidRequestEnvironment(payload) { - const __navigator = JSON.parse(JSON.stringify(recur(navigator))); + const __navigator = deepClone(recur(navigator)); delete __navigator.plugins; deepSetValue(payload, 'environment.ri', getRefererInfo()); deepSetValue(payload, 'environment.hl', window.history.length); @@ -232,6 +233,7 @@ function hasValidParameters(bidRequest) { export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, isBidRequestValid: function(bidRequest) { logInfo('bid: ', bidRequest); diff --git a/modules/airgridRtdProvider.js b/modules/airgridRtdProvider.js index 079628c88fc..300744d62fe 100644 --- a/modules/airgridRtdProvider.js +++ b/modules/airgridRtdProvider.js @@ -43,7 +43,7 @@ export function attachScriptTagToDOM(rtdConfig) { edktInitializor.apiKey = rtdConfig.params.apiKey; edktInitializor.invoked = true; const moduleSrc = getModuleUrl(rtdConfig.params.accountId); - loadExternalScript(moduleSrc, SUBMODULE_NAME); + loadExternalScript(moduleSrc, MODULE_TYPE_RTD, SUBMODULE_NAME); } } diff --git a/modules/ampliffyBidAdapter.js b/modules/ampliffyBidAdapter.js index bcd28e5bcf1..e79b04ab4c4 100644 --- a/modules/ampliffyBidAdapter.js +++ b/modules/ampliffyBidAdapter.js @@ -278,7 +278,6 @@ function extractTrackingURL(htmlContent, ret) { const trackingUrlDiv = htmlContent.querySelectorAll('[bidder-tracking-url]')[0]; if (trackingUrlDiv) { const trackingUrl = trackingUrlDiv.getAttribute('bidder-tracking-url'); - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML: trackingUrl: ', trackingUrl) ret.trackingUrl = trackingUrl; } @@ -304,10 +303,8 @@ export function parseXML(xml, bid) { ret.userSyncs = extractSyncs(htmlContent); } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'Error parsing XML', e); } - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'parseXML RET:', ret); return ret; @@ -341,7 +338,6 @@ export function isAllowedToBidUp(html, currentURL) { } } } catch (e) { - // eslint-disable-next-line no-console logError(LOG_PREFIX + 'isAllowedToBidUp', e); } return allowedToPush; @@ -399,7 +395,6 @@ function onBidWon(bid) { } } function onTimeOut() { - // eslint-disable-next-line no-console logInfo(LOG_PREFIX + 'TIMEOUT'); } diff --git a/modules/amxBidAdapter.js b/modules/amxBidAdapter.js index 6e14f65b0c8..508c4d6a0c7 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -142,7 +142,7 @@ function getFloor(bid) { size: '*', bidRequest: bid, }); - return floor.floor; + return floor?.floor; } catch (e) { logError('call to getFloor failed: ', e); return DEFAULT_MIN_FLOOR; @@ -454,6 +454,10 @@ export const spec = { setUIDSafe(response.am); } + const bidderSettings = config.getConfig('bidderSettings'); + const settings = bidderSettings?.amx ?? bidderSettings?.standard ?? {}; + const allowAlternateBidderCodes = !!settings.allowAlternateBidderCodes; + return flatMap(Object.keys(response.r), (bidID) => { return flatMap(response.r[bidID], (siteBid) => siteBid.b.map((bid) => { @@ -466,8 +470,10 @@ export const spec = { const size = resolveSize(bid, request.data, bidID); const defaultExpiration = mediaType === BANNER ? 240 : 300; + const { bc: bidderCode, ds: demandSource } = bid.ext ?? {}; return { + ...(bidderCode != null && allowAlternateBidderCodes ? { bidderCode } : {}), requestId: bidID, cpm: bid.price, width: size[0], @@ -479,6 +485,7 @@ export const spec = { meta: { advertiserDomains: bid.adomain, mediaType, + ...(demandSource != null ? { demandSource } : {}), }, mediaType, ttl: typeof bid.exp === 'number' ? bid.exp : defaultExpiration, diff --git a/modules/anPspParamsConverter.js b/modules/anPspParamsConverter.js new file mode 100644 index 00000000000..27b90168476 --- /dev/null +++ b/modules/anPspParamsConverter.js @@ -0,0 +1,128 @@ +/* +- register a hook function on the makeBidRequests hook (after the main function ran) + +- this hook function will: +1. verify s2sconfig is defined and we (or our aliases) are included to the config +2. filter bidRequests that match to our bidderName or any registered aliases +3. for each request, read the bidderRequests.bids[].params to modify the keys/values + a. in particular change the keywords structure, apply underscore casing for keys, adjust use_payment_rule name, and convert certain values' types + b. will import some functions from the anKeywords library, but ideally should be kept separate to avoid including this code when it's not needed (strict client-side setups) and avoid the rest of the appnexus adapter's need for inclusion for those strictly server-side setups. +*/ + +// import { CONSTANTS } from '../src/cons tants.js'; +import {isArray, isPlainObject, isStr} from '../src/utils.js'; +import {getHook} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {convertCamelToUnderscore, appnexusAliases} from '../libraries/appnexusUtils/anUtils.js'; +import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; +import adapterManager from '../src/adapterManager.js'; + +// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' +function convertKeywordsToString(keywords) { + let result = ''; + Object.keys(keywords).forEach(key => { + // if 'text' or '' + if (isStr(keywords[key])) { + if (keywords[key] !== '') { + result += `${key}=${keywords[key]},` + } else { + result += `${key},`; + } + } else if (isArray(keywords[key])) { + if (keywords[key][0] === '') { + result += `${key},` + } else { + keywords[key].forEach(val => { + result += `${key}=${val},` + }); + } + } + }); + + // remove last trailing comma + result = result.substring(0, result.length - 1); + return result; +} + +function digForAppNexusBidder(s2sConfig) { + let result = false; + // check for plain setup + if (s2sConfig?.bidders?.includes('appnexus')) result = true; + + // registered aliases + const aliasList = appnexusAliases.map(aliasObj => (aliasObj.code)); + if (!result && s2sConfig?.bidders?.filter(s2sBidder => aliasList.includes(s2sBidder)).length > 0) result = true; + + // pbjs.aliasBidder + if (!result) { + result = !!(s2sConfig?.bidders?.find(bidder => (adapterManager.resolveAlias(bidder) === 'appnexus'))); + } + + return result; +} + +// need a separate check b/c we're checking a specific bidRequest to see if we modify it, not just that we have a server-side bidder somewhere in prebid.js +// function isThisOurBidderInDisguise(tarBidder, s2sConfig) { +// if (tarBidder === 'appnexus') return true; + +// if (isPlainObject(s2sConfig?.extPrebid?.aliases) && !!(Object.entries(s2sConfig?.extPrebid?.aliases).find((pair) => (pair[0] === tarBidder && pair[1] === 'appnexus')))) return true; + +// if (appnexusAliases.map(aliasObj => (aliasObj.code)).includes(tarBidder)) return true; + +// if (adapterManager.resolveAlias(tarBidder) === 'appnexus') return true; + +// return false; +// } + +export function convertAnParams(next, bidderRequests) { + // check s2sconfig + const s2sConfig = config.getConfig('s2sConfig'); + let proceed = false; + + if (isPlainObject(s2sConfig)) { + proceed = digForAppNexusBidder(s2sConfig); + } else if (isArray(s2sConfig)) { + s2sConfig.forEach(s2sCfg => { + proceed = digForAppNexusBidder(s2sCfg); + }); + } + + if (proceed) { + bidderRequests + .flatMap(br => br.bids) + .filter(bid => bid.src === 's2s' && adapterManager.resolveAlias(bid.bidder) === 'appnexus') + .forEach((bid) => { + transformBidParams(bid); + }); + } + + next(bidderRequests); +} + +function transformBidParams(bid) { + let params = bid.params; + if (params) { + params = convertTypes({ + 'member': 'string', + 'invCode': 'string', + 'placementId': 'number', + 'keywords': convertKeywordsToString, + 'publisherId': 'number' + }, params); + + Object.keys(params).forEach(paramKey => { + let convertedKey = convertCamelToUnderscore(paramKey); + if (convertedKey !== paramKey) { + params[convertedKey] = params[paramKey]; + delete params[paramKey]; + } + }); + + params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; + if (params.use_payment_rule) { + delete params.use_payment_rule; + } + } +} + +getHook('makeBidRequests').after(convertAnParams, 9); diff --git a/modules/anPspParamsConverter.md b/modules/anPspParamsConverter.md new file mode 100644 index 00000000000..f341b0a5976 --- /dev/null +++ b/modules/anPspParamsConverter.md @@ -0,0 +1,10 @@ +## Quick Summary + +This module is a temporary measure for publishers running Prebid.js 9.0+ and using the AppNexus PSP endpoint through their Prebid.js setup. Please ensure to include this module in your builds of Prebid.js 9.0+, otherwise requests to PSP may not complete successfully. + +## Module's purpose + +This module replicates certain functionality that was previously stored in the appnexusBidAdapter.js file within a function named transformBidParams. + +This transformBidParams was a standard function in all adapters, which helped to change/modify the params and their values to a format that matched the bidder's request structure on the server-side endpoint. In Prebid.js 9.0, this standard function was removed in all adapter files, so that the whole client-side file (eg appnexusBidAdapter.js) wouldn't have to be included in a prebid.js build file that was meant for server-side bidders. + diff --git a/modules/aniviewBidAdapter.js b/modules/aniviewBidAdapter.js index 84552638421..e5baac1add0 100644 --- a/modules/aniviewBidAdapter.js +++ b/modules/aniviewBidAdapter.js @@ -1,317 +1,310 @@ import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { Renderer } from '../src/Renderer.js'; -import { logError } from '../src/utils.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { + deepAccess, + deepSetValue, + mergeDeep, + isFn, + isStr, + isEmptyStr, + isPlainObject, + getUniqueIdentifierStr +} from '../src/utils.js'; const BIDDER_CODE = 'aniview'; const GVLID = 780; const TTL = 600; +const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_PLAYER_DOMAIN = 'player.aniview.com'; +const SSP_ENDPOINT = 'https://rtb.aniview.com/sspRTB2'; +const RENDERER_FILENAME = 'prebidRenderer.js'; -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 - }); - }); -} +const converter = ortbConverter({ + context: { + netRevenue: true, // required + ttl: TTL, // required + currency: DEFAULT_CURRENCY, + }, -function newRenderer(bidRequest) { - let playerDomain = 'player.aniview.com'; - const config = {}; + imp(buildImp, bidRequest, context) { + const { mediaType } = context; + const imp = buildImp(bidRequest, context); + const isBanner = mediaType === BANNER; + const { width, height } = getSize(context, bidRequest); + const floor = getFloor(bidRequest, { width, height }, mediaType); - if (bidRequest && bidRequest.bidRequest && bidRequest.bidRequest.params) { - const params = bidRequest.bidRequest.params + imp.tagid = deepAccess(bidRequest, 'params.AV_CHANNELID'); - if (params.playerDomain) { - playerDomain = params.playerDomain; + if (floor) { + imp.bidfloor = floor; + imp.bidfloorcur = DEFAULT_CURRENCY; } - if (params.AV_PUBLISHERID) { - config.AV_PUBLISHERID = params.AV_PUBLISHERID; + if (isBanner) { + // TODO: remove once serving will be fixed + deepSetValue(imp, 'banner', { w: width, h: height }); } - if (params.AV_CHANNELID) { - config.AV_CHANNELID = params.AV_CHANNELID; - } - } + return imp; + }, - const renderer = Renderer.install({ - url: 'https://' + playerDomain + '/script/6.1/prebidRenderer.js', - config: config, - loaded: false, - }); + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); - try { - renderer.setRender(avRenderer); - } catch (err) { - } + mergeDeep(request, { + ext: { + [BIDDER_CODE]: { + pbjs: 1, + pbv: '$prebid.version$', + } + } + }) - return renderer; -} + return request; + }, -function isBidRequestValid(bid) { - if (!bid.params || !bid.params.AV_PUBLISHERID || !bid.params.AV_CHANNELID) { return false; } + bidResponse(buildBidResponse, bid, context) { + const { bidRequest, mediaType } = context; + const { width, height } = getSize(context, bidRequest); + const isVideoBid = mediaType === VIDEO; + const isBannerBid = mediaType === BANNER; - 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 (isVideoBid) { + context.vastXml = bid.adm; } - if (sizes.length === 2 && typeof sizes[0] === 'number') { - sizes = [[sizes[0], sizes[1]]]; + + const bidResponse = buildBidResponse(bid, context); + + if (isEmptyStr(bidRequest?.bidId) || !bid.adm || bidResponse.cpm <= 0) { + return bidResponse; } - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - let playerWidth; - let playerHeight; + mergeDeep(bidResponse, { + width, + height, + creativeId: bid.crid || 'creativeId', + meta: { advertiserDomains: [] }, + adId: getUniqueIdentifierStr(), + }); - if (size && size.length == 2) { - playerWidth = size[0]; - playerHeight = size[1]; + if (isVideoBid) { + if (bidRequest.mediaTypes.video.context === 'outstream') { + bidResponse.renderer = createRenderer(bidRequest); + } + } else if (isBannerBid) { + if (bid.adm?.trim().startsWith(' { + Object.keys(bidRequest.mediaTypes).forEach((mediaType) => { + const endpoint = bidRequest.params.dev?.endpoint || SSP_ENDPOINT; - bidRequests.push({ - method: 'GET', - url: servingUrl, - data: s2sParams, - bidRequest + requests.push({ + method: 'POST', + url: endpoint, + bids: [bidRequest], + options: { withCredentials: false }, + data: converter.toORTB({ + bidderRequest, + bidRequests: [bidRequest], + context: { mediaType }, + }), + }); }); + }); + + return requests; + }, + + interpretResponse(serverResponse, bidderRequest) { + const { bids, data } = bidderRequest; + const { body } = serverResponse; + const bidResponse = body?.seatbid?.[0]?.bid?.[0]; + + if (!bids || !data || !bidResponse) { + return []; } - } - 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; - } + const response = converter.fromORTB({ response: body, request: data }); + + return response.bids.map(bid => { + const replacements = { + auctionPrice: bid.cpm, + auctionId: bid.requestId, + auctionBidId: bidResponse.impid, + auctionImpId: bidResponse.impid, + auctionSeatId: bid.seatBidId, + auctionAdId: bid.adId, + }; + + const bidAdmWithReplacedMacros = replaceMacros(bidResponse.adm, replacements); + + if (bid.mediaType === VIDEO) { + bid.vastXml = bidAdmWithReplacedMacros; + + if (bidResponse?.nurl) { + bid.vastUrl = replaceMacros(bidResponse.nurl, replacements); } + } else { + bid.ad = bidAdmWithReplacedMacros; } + + return bid; + }); + }, + + getUserSyncs(syncOptions, serverResponses) { + if (!serverResponses?.[0]?.body || serverResponses.error) { + return []; } - } - return ret; + + try { + const syncs = serverResponses[0].body.ext?.[BIDDER_CODE]?.sync; + + if (syncs) { + return getValidSyncs(syncs, syncOptions); + } + } catch (error) {} + + return []; + }, +}; + +function getValidSyncs(syncs, options) { + return syncs + .filter(sync => isSyncValid(sync, options)) + .map(sync => processSync(sync)) || []; } -function buildBanner(xmlStr, bidRequest, bidResponse) { - var rendererData = JSON.stringify({ - id: bidRequest.adUnitCode, - debug: window.location.href.indexOf('pbjsDebug') >= 0, - placement: bidRequest.bidRequest.adUnitCode, - width: bidResponse.width, - height: bidResponse.height, - vastXml: xmlStr, - bid: bidResponse, - config: bidRequest.bidRequest.params.rendererConfig - }); - var playerDomain = bidRequest.bidRequest.params.playerDomain || 'player.aniview.com'; - var ad = ''; - ad += '' - return ad; + +function isSyncValid(sync, options) { + return isPlainObject(sync) && + isStr(sync.url) && + (sync.e === 'inventory' || sync.e === 'sync') && + ((sync.t === 1 && options?.pixelEnabled) || (sync.t === 3 && options?.iframeEnabled)); } -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 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) { - let cpmData = getCpmData(xml); - if (cpmData.cpm > 0) { - bidResponse.requestId = bidRequest.data.bidId; - 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; - bidResponse.mediaType = mediaType; - if (mediaType === VIDEO) { - try { - var blob = new Blob([xmlStr], { - type: 'application/xml' - }); - bidResponse.vastUrl = window.URL.createObjectURL(blob); - } catch (ex) { - logError('Aniview Debug create vastXml error:\n\n' + ex); - } - bidResponse.vastXml = xmlStr; - if (bidRequest.bidRequest && bidRequest.bidRequest.mediaTypes && bidRequest.bidRequest.mediaTypes.video && bidRequest.bidRequest.mediaTypes.video.context === 'outstream') { - bidResponse.renderer = newRenderer(bidRequest); - } - } else { - bidResponse.ad = buildBanner(xmlStr, bidRequest, bidResponse); - } - bidResponse.meta = { - advertiserDomains: [] - }; - - bidResponses.push(bidResponse); - } - } else {} - } else {} - } catch (e) {} - } - } else {} - return bidResponses; +function processSync(sync) { + return { url: sync.url, type: sync.t === 1 ? 'image' : 'iframe' }; } -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' || data[j].e === 'sync') - ) { - 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; - } - } - } - } +function getSize(context, bid) { + const isVideoBid = context.mediaType === VIDEO; + let size = [640, 480]; + + if (isVideoBid && bid.mediaTypes?.video?.playerSize?.length) { + size = bid.mediaTypes.video.playerSize[0]; + } else if (bid.sizes?.length) { + size = bid.sizes[0]; } - return ret; + + return { + width: size[0], + height: size[1], + }; } -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) {} +// https://docs.prebid.org/dev-docs/modules/floors.html#example-getfloor-scenarios +function getFloor(bid, size, mediaType) { + if (!isFn(bid?.getFloor)) { + return null; + } + + try { + const bidFloor = bid.getFloor({ + currency: DEFAULT_CURRENCY, + mediaType, // or '*' for all media types + size: [size.width, size.height], // or '*' for all sizes + }); + + if (isPlainObject(bidFloor) && !isNaN(bidFloor.floor) && bidFloor.currency === DEFAULT_CURRENCY) { + return bidFloor.floor; } + } catch {} + + return null; +} + +function replaceMacros(str, replacements) { + if (!replacements || !isStr(str)) { + return str; } + + return str + .replaceAll(`\${AUCTION_PRICE}`, replacements.auctionPrice || '') + .replaceAll(`\${AUCTION_ID}`, replacements.auctionId || '') + .replaceAll(`\${AUCTION_BID_ID}`, replacements.auctionBidId || '') + .replaceAll(`\${AUCTION_IMP_ID}`, replacements.auctionImpId || '') + .replaceAll(`\${AUCTION_SEAT_ID}`, replacements.auctionSeatId || '') + .replaceAll(`\${AUCTION_AD_ID}`, replacements.auctionAdId || ''); } -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: ['avantisvideo', 'selectmediavideo', 'vidcrunch', 'openwebvideo', 'didnavideo', 'ottadvisors', 'pgammedia'], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; +function createRenderer(bidRequest) { + const config = {}; + const { params = {} } = bidRequest; + const playerDomain = params.playerDomain || DEFAULT_PLAYER_DOMAIN; + + if (params.AV_PUBLISHERID) { + config.AV_PUBLISHERID = params.AV_PUBLISHERID; + } + + if (params.AV_CHANNELID) { + config.AV_CHANNELID = params.AV_CHANNELID; + } + + const renderer = Renderer.install({ + url: `https://${playerDomain}/script/6.1/${RENDERER_FILENAME}`, + config, + loaded: false, + }); + + try { + renderer.setRender(avRenderer); + } catch (error) {} + + return renderer; +} + +function avRenderer(bid) { + bid.renderer.push(function() { + const eventsCallback = bid?.renderer?.handleVideoEvent ?? null; + const { ad, adId, adUnitCode, vastUrl, vastXml, width, height, params = [] } = bid; + + window.aniviewRenderer.renderAd({ + id: adUnitCode + '_' + adId, + debug: window.location.href.indexOf('pbjsDebug') >= 0, + placement: adUnitCode, + config: params[0]?.rendererConfig, + width, + height, + vastUrl, + vastXml: vastXml || ad, + eventsCallback, + bid, + }); + }); +} registerBidder(spec); diff --git a/modules/aniviewBidAdapter.md b/modules/aniviewBidAdapter.md index 63c91ca009a..b067166b080 100644 --- a/modules/aniviewBidAdapter.md +++ b/modules/aniviewBidAdapter.md @@ -1,16 +1,16 @@ # Overview ``` -Module Name: ANIVIEW Bidder Adapter +Module Name: Aniview Bidder Adapter Module Type: Bidder Adapter Maintainer: support@aniview.com ``` # Description -Connects to ANIVIEW Ad server for bids. +Connects to Aniview Ad server for bids. -ANIVIEW bid adapter supports Banner and Video 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). @@ -34,5 +34,3 @@ var videoAdUnit = [ }] }]; ``` - -``` diff --git a/modules/anonymisedRtdProvider.js b/modules/anonymisedRtdProvider.js index 48ac649f002..c3bb21ce8a5 100644 --- a/modules/anonymisedRtdProvider.js +++ b/modules/anonymisedRtdProvider.js @@ -9,10 +9,13 @@ import {getStorageManager} from '../src/storageManager.js'; import {submodule} from '../src/hook.js'; import {isPlainObject, mergeDeep, logMessage, logError} from '../src/utils.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; - +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ export function createRtdProvider(moduleName) { const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = moduleName; + const GVLID = 1116; const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); /** @@ -106,6 +109,7 @@ export function createRtdProvider(moduleName) { /** @type {RtdSubmodule} */ const rtdSubmodule = { name: SUBMODULE_NAME, + gvlid: GVLID, getBidRequestData: getRealTimeData, init: init }; diff --git a/modules/anonymisedRtdProvider.md b/modules/anonymisedRtdProvider.md index 2ff2597690b..526c75d7fb7 100644 --- a/modules/anonymisedRtdProvider.md +++ b/modules/anonymisedRtdProvider.md @@ -8,7 +8,7 @@ Anonymised’s Real-time Data Provider automatically obtains segment IDs from th - Build the anonymisedRtd module into the Prebid.js package with: ```bash - gulp build --modules=anonymisedRtdProvider,... + gulp build --modules=rtdModule,anonymisedRtdProvider,... ``` - Use `setConfig` to instruct Prebid.js to initilaize the anonymisedRtdProvider module, as specified below. @@ -24,7 +24,7 @@ Anonymised’s Real-time Data Provider automatically obtains segment IDs from th waitForIt: true, params: { cohortStorageKey: "cohort_ids", - bidders: ["smartadserver", "appnexus"], + bidders: ["appnexus", "onetag", "pubmatic", "smartadserver", ...], segtax: 1000 } } diff --git a/modules/anyclipBidAdapter.js b/modules/anyclipBidAdapter.js index cb9103899a4..8a5906ebc93 100644 --- a/modules/anyclipBidAdapter.js +++ b/modules/anyclipBidAdapter.js @@ -1,213 +1,54 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {deepAccess, isArray, isFn, logError, logInfo} from '../src/utils.js'; -import {config} from '../src/config.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest - * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec - */ +import { + buildRequests, + getUserSyncs, + interpretResponse, +} from '../libraries/xeUtils/bidderUtils.js'; +import {deepAccess, getBidIdParameter, isArray, logError} from '../src/utils.js'; const BIDDER_CODE = 'anyclip'; -const ENDPOINT_URL = 'https://prebid.anyclip.com'; -const DEFAULT_CURRENCY = 'USD'; -const NET_REVENUE = false; +const ENDPOINT = 'https://prebid.anyclip.com'; + +function isBidRequestValid(bid) { + if (bid && typeof bid.params !== 'object') { + logError('Params is not defined or is incorrect in the bidder settings'); + return false; + } -/* - * Get the bid floor value from the bidRequest object, either using the getFloor function or by accessing the 'params.floor' property. - * If the bid floor cannot be determined, return 0 as a fallback value. - */ -function getBidFloor(bidRequest) { - if (!isFn(bidRequest.getFloor)) { - return deepAccess(bidRequest, 'params.floor', 0); + if (!getBidIdParameter('publisherId', bid.params) || !getBidIdParameter('supplyTagId', bid.params)) { + logError('PublisherId or supplyTagId is not present in bidder params'); + return false; } - try { - const bidFloor = bidRequest.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; + if (deepAccess(bid, 'mediaTypes.video') && !isArray(deepAccess(bid, 'mediaTypes.video.playerSize'))) { + logError('mediaTypes.video.playerSize is required for video'); + return false; } + + return true; } -/** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - - /** - * @param {object} bid - * @return {boolean} - */ - isBidRequestValid: (bid = {}) => { - const bidder = deepAccess(bid, 'bidder'); - const params = deepAccess(bid, 'params', {}); - const mediaTypes = deepAccess(bid, 'mediaTypes', {}); - const banner = deepAccess(mediaTypes, BANNER, {}); - - const isValidBidder = (bidder === BIDDER_CODE); - const isValidSize = (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); - const hasSizes = mediaTypes[BANNER] ? isValidSize : false; - const hasRequiredBidParams = Boolean(params.publisherId && params.supplyTagId); - - const isValid = isValidBidder && hasSizes && hasRequiredBidParams; - if (!isValid) { - logError(`Invalid bid request: isValidBidder: ${isValidBidder}, hasSizes: ${hasSizes}, hasRequiredBidParams: ${hasRequiredBidParams}`); - } - return isValid; - }, - - /** - * @param {BidRequest[]} validBidRequests - * @param {*} bidderRequest - * @return {ServerRequest} - */ + aliases: ['anyclip'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, buildRequests: (validBidRequests, bidderRequest) => { - const bidRequest = validBidRequests[0]; - - let refererInfo; - if (bidderRequest && bidderRequest.refererInfo) { - refererInfo = bidderRequest.refererInfo; - } - - const timeout = bidderRequest.timeout; - const timeoutAdjustment = timeout - ((20 / 100) * timeout); // timeout adjustment - 20% - - if (isPubTagAvailable()) { - // Options - const options = { - publisherId: bidRequest.params.publisherId, - supplyTagId: bidRequest.params.supplyTagId, - url: refererInfo.page, - domain: refererInfo.domain, - prebidTimeout: timeoutAdjustment, - gpid: bidRequest.adUnitCode, - ext: { - transactionId: bidRequest.transactionId - }, - sizes: bidRequest.sizes.map((size) => { - return {width: size[0], height: size[1]} - }) - } - // Floor - const floor = parseFloat(getBidFloor(bidRequest)); - if (!isNaN(floor)) { - options.ext.floor = floor; - } - // Supply Chain (Schain) - if (bidRequest?.schain) { - options.schain = bidRequest.schain - } - // GDPR & Consent (EU) - if (bidderRequest?.gdprConsent) { - options.gdpr = (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); - options.consent = bidderRequest.gdprConsent.consentString; - } - // GPP - if (bidderRequest?.gppConsent?.gppString) { - options.gpp = { - gppVersion: bidderRequest.gppConsent.gppVersion, - sectionList: bidderRequest.gppConsent.sectionList, - applicableSections: bidderRequest.gppConsent.applicableSections, - gppString: bidderRequest.gppConsent.gppString - } - } - // CCPA (US Privacy) - if (bidderRequest?.uspConsent) { - options.usPrivacy = bidderRequest.uspConsent; - } - // COPPA - if (config.getConfig('coppa') === true) { - options.coppa = 1; - } - // Eids - if (bidRequest?.userIdAsEids) { - const eids = bidRequest.userIdAsEids; - if (eids && eids.length) { - options.eids = eids; - } - } - - // Request bids - const requestBidsPromise = window._anyclip.pubTag.requestBids(options); - if (requestBidsPromise !== undefined) { - requestBidsPromise - .then(() => { - logInfo('PubTag requestBids done'); - }) - .catch((err) => { - logError('PubTag requestBids error', err); - }); - } - - // Request - const payload = { - tmax: timeoutAdjustment - } - - return { - method: 'GET', - url: ENDPOINT_URL, - data: payload, - bidRequest - } - } + const builtRequests = buildRequests(validBidRequests, bidderRequest, ENDPOINT) + const requests = JSON.parse(builtRequests.data) + const updatedRequests = requests.map(req => ({ + ...req, + env: { + publisherId: validBidRequests[0].params.publisherId, + supplyTagId: validBidRequests[0].params.supplyTagId, + floor: req.floor + }, + })) + return {...builtRequests, data: JSON.stringify(updatedRequests)} }, - - /** - * @param {*} serverResponse - * @param {ServerRequest} bidRequest - * @return {Bid[]} - */ - interpretResponse: (serverResponse, { bidRequest }) => { - const bids = []; - - if (bidRequest && isPubTagAvailable()) { - const bidResponse = window._anyclip.pubTag.getBids(bidRequest.transactionId); - if (bidResponse) { - const { adServer } = bidResponse; - if (adServer) { - bids.push({ - requestId: bidRequest.bidId, - creativeId: adServer.bid.creativeId, - cpm: bidResponse.cpm, - width: adServer.bid.width, - height: adServer.bid.height, - currency: adServer.bid.currency || DEFAULT_CURRENCY, - netRevenue: NET_REVENUE, - ttl: adServer.bid.ttl, - ad: adServer.bid.ad, - meta: adServer.bid.meta - }); - } - } - } - - return bids; - }, - - /** - * @param {Bid} bid - */ - onBidWon: (bid) => { - if (isPubTagAvailable()) { - window._anyclip.pubTag.bidWon(bid); - } - } -} - -/** - * @return {boolean} - */ -const isPubTagAvailable = () => { - return !!(window._anyclip && window._anyclip.pubTag); + interpretResponse, + getUserSyncs } registerBidder(spec); diff --git a/modules/apacdexBidAdapter.js b/modules/apacdexBidAdapter.js index dadbdb72e95..83119052f3a 100644 --- a/modules/apacdexBidAdapter.js +++ b/modules/apacdexBidAdapter.js @@ -1,7 +1,7 @@ -import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError } from '../src/utils.js'; +import { deepAccess, isPlainObject, isArray, replaceAuctionPrice, isFn, logError, deepClone } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {parseDomain} from '../src/refererDetection.js'; const BIDDER_CODE = 'apacdex'; const ENDPOINT = 'https://useast.quantumdex.io/auction/pbjs' @@ -85,7 +85,7 @@ export const spec = { bidReq.bidFloor = bidFloor; } - bids.push(JSON.parse(JSON.stringify(bidReq))); + bids.push(deepClone(bidReq)); }); const payload = {}; diff --git a/modules/appnexusBidAdapter.js b/modules/appnexusBidAdapter.js index 551bf5401e2..4270b47d91e 100644 --- a/modules/appnexusBidAdapter.js +++ b/modules/appnexusBidAdapter.js @@ -15,7 +15,8 @@ import { logError, logInfo, logMessage, - logWarn + logWarn, + mergeDeep } from '../src/utils.js'; import {Renderer} from '../src/Renderer.js'; import {config} from '../src/config.js'; @@ -25,16 +26,15 @@ import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; import { convertKeywordStringToANMap, getANKewyordParamFromMaps, - getANKeywordParam, - transformBidderParamKeywords + getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; -import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; +import {convertCamelToUnderscore, fill, appnexusAliases} from '../libraries/appnexusUtils/anUtils.js'; import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; @@ -104,25 +104,33 @@ const VIEWABILITY_URL_START = /\/\/cdn\.adnxs\.com\/v|\/\/cdn\.adnxs\-simple\.co const VIEWABILITY_FILE_NAME = 'trk.js'; const GVLID = 32; const storage = getStorageManager({bidderCode: BIDDER_CODE}); +// ORTB2 device types according to the OpenRTB specification +const ORTB2_DEVICE_TYPE = { + MOBILE_TABLET: 1, + PERSONAL_COMPUTER: 2, + CONNECTED_TV: 3, + PHONE: 4, + TABLET: 5, + CONNECTED_DEVICE: 6, + SET_TOP_BOX: 7, + OOH_DEVICE: 8 +}; +// Map of ORTB2 device types to AppNexus device types +const ORTB2_DEVICE_TYPE_MAP = new Map([ + [ORTB2_DEVICE_TYPE.MOBILE_TABLET, 'Mobile/Tablet - General'], + [ORTB2_DEVICE_TYPE.PERSONAL_COMPUTER, 'Personal Computer'], + [ORTB2_DEVICE_TYPE.CONNECTED_TV, 'Connected TV'], + [ORTB2_DEVICE_TYPE.PHONE, 'Phone'], + [ORTB2_DEVICE_TYPE.TABLET, 'Tablet'], + [ORTB2_DEVICE_TYPE.CONNECTED_DEVICE, 'Connected Device'], + [ORTB2_DEVICE_TYPE.SET_TOP_BOX, 'Set Top Box'], + [ORTB2_DEVICE_TYPE.OOH_DEVICE, 'OOH Device'], +]); export const spec = { code: BIDDER_CODE, gvlid: GVLID, - aliases: [ - { code: 'appnexusAst', gvlid: 32 }, - { code: 'emxdigital', gvlid: 183 }, - { code: 'emetriq', gvlid: 213 }, - { code: 'pagescience', gvlid: 32 }, - { code: 'gourmetads', gvlid: 32 }, - { code: 'matomy', gvlid: 32 }, - { code: 'featureforward', gvlid: 32 }, - { code: 'oftmedia', gvlid: 32 }, - { code: 'adasta', gvlid: 32 }, - { code: 'beintoo', gvlid: 618 }, - { code: 'projectagora', gvlid: 1032 }, - { code: 'uol', gvlid: 32 }, - { code: 'adzymic', gvlid: 32 }, - ], + aliases: appnexusAliases, supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -262,6 +270,12 @@ export const spec = { payload.app = appIdObj; } + // if present, convert and merge device object from ortb2 into `payload.device` + if (bidderRequest?.ortb2?.device) { + payload.device = payload.device || {}; + mergeDeep(payload.device, convertORTB2DeviceDataToAppNexusDeviceObject(bidderRequest.ortb2.device)); + } + // grab the ortb2 keyword data (if it exists) and convert from the comma list string format to object format let ortb2 = deepClone(bidderRequest && bidderRequest.ortb2); @@ -438,62 +452,21 @@ export const spec = { }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { - function checkGppStatus(gppConsent) { - // user sync suppression for adapters is handled in activity controls and not needed in adapters - return true; - } - - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { return [{ type: 'iframe', url: 'https://acdn.adnxs.com/dmp/async_usersync.html' }]; } - }, - transformBidParams: function (params, isOpenRtb, adUnit, bidRequests) { - let conversionFn = transformBidderParamKeywords; - if (isOpenRtb === true) { - let s2sEndpointUrl = null; - let s2sConfig = config.getConfig('s2sConfig'); - - if (isPlainObject(s2sConfig)) { - s2sEndpointUrl = deepAccess(s2sConfig, 'endpoint.p1Consent'); - } else if (isArray(s2sConfig)) { - s2sConfig.forEach(s2sCfg => { - if (includes(s2sCfg.bidders, adUnit.bids[0].bidder)) { - s2sEndpointUrl = deepAccess(s2sCfg, 'endpoint.p1Consent'); - } - }); - } - - if (s2sEndpointUrl && s2sEndpointUrl.match('/openrtb2/prebid')) { - conversionFn = convertKeywordsToString; - } + if (syncOptions.pixelEnabled) { + // first attempt using static list + const imgList = ['https://px.ads.linkedin.com/setuid?partner=appNexus']; + return imgList.map(url => ({ + type: 'image', + url + })); } - - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': conversionFn, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - - params.use_pmt_rule = (typeof params.use_payment_rule === 'boolean') ? params.use_payment_rule : false; - if (params.use_payment_rule) { delete params.use_payment_rule; } - } - - return params; } }; @@ -721,19 +694,124 @@ function newBid(serverBid, rtbBid, bidderRequest) { javascriptTrackers: jsTrackers }; if (nativeAd.main_img) { - bid['native'].image = { + bid[NATIVE].image = { url: nativeAd.main_img.url, height: nativeAd.main_img.height, width: nativeAd.main_img.width, }; } if (nativeAd.icon) { - bid['native'].icon = { + bid[NATIVE].icon = { url: nativeAd.icon.url, height: nativeAd.icon.height, width: nativeAd.icon.width, }; } + + // Custom fields + bid[NATIVE].ext = { + video: nativeAd.video, + customImage1: nativeAd.image1 && { + url: nativeAd.image1.url, + height: nativeAd.image1.height, + width: nativeAd.image1.width, + }, + customImage2: nativeAd.image2 && { + url: nativeAd.image2.url, + height: nativeAd.image2.height, + width: nativeAd.image2.width, + }, + customImage3: nativeAd.image3 && { + url: nativeAd.image3.url, + height: nativeAd.image3.height, + width: nativeAd.image3.width, + }, + customImage4: nativeAd.image4 && { + url: nativeAd.image4.url, + height: nativeAd.image4.height, + width: nativeAd.image4.width, + }, + customImage5: nativeAd.image5 && { + url: nativeAd.image5.url, + height: nativeAd.image5.height, + width: nativeAd.image5.width, + }, + customIcon1: nativeAd.icon1 && { + url: nativeAd.icon1.url, + height: nativeAd.icon1.height, + width: nativeAd.icon1.width, + }, + customIcon2: nativeAd.icon2 && { + url: nativeAd.icon2.url, + height: nativeAd.icon2.height, + width: nativeAd.icon2.width, + }, + customIcon3: nativeAd.icon3 && { + url: nativeAd.icon3.url, + height: nativeAd.icon3.height, + width: nativeAd.icon3.width, + }, + customIcon4: nativeAd.icon4 && { + url: nativeAd.icon4.url, + height: nativeAd.icon4.height, + width: nativeAd.icon4.width, + }, + customIcon5: nativeAd.icon5 && { + url: nativeAd.icon5.url, + height: nativeAd.icon5.height, + width: nativeAd.icon5.width, + }, + customSocialIcon1: nativeAd.socialicon1 && { + url: nativeAd.socialicon1.url, + height: nativeAd.socialicon1.height, + width: nativeAd.socialicon1.width, + }, + customSocialIcon2: nativeAd.socialicon2 && { + url: nativeAd.socialicon2.url, + height: nativeAd.socialicon2.height, + width: nativeAd.socialicon2.width, + }, + customSocialIcon3: nativeAd.socialicon3 && { + url: nativeAd.socialicon3.url, + height: nativeAd.socialicon3.height, + width: nativeAd.socialicon3.width, + }, + customSocialIcon4: nativeAd.socialicon4 && { + url: nativeAd.socialicon4.url, + height: nativeAd.socialicon4.height, + width: nativeAd.socialicon4.width, + }, + customSocialIcon5: nativeAd.socialicon5 && { + url: nativeAd.socialicon5.url, + height: nativeAd.socialicon5.height, + width: nativeAd.socialicon5.width, + }, + customTitle1: nativeAd.title1, + customTitle2: nativeAd.title2, + customTitle3: nativeAd.title3, + customTitle4: nativeAd.title4, + customTitle5: nativeAd.title5, + customBody1: nativeAd.body1, + customBody2: nativeAd.body2, + customBody3: nativeAd.body3, + customBody4: nativeAd.body4, + customBody5: nativeAd.body5, + customCta1: nativeAd.ctatext1, + customCta2: nativeAd.ctatext2, + customCta3: nativeAd.ctatext3, + customCta4: nativeAd.ctatext4, + customCta5: nativeAd.ctatext5, + customDisplayUrl1: nativeAd.displayurl1, + customDisplayUrl2: nativeAd.displayurl2, + customDisplayUrl3: nativeAd.displayurl3, + customDisplayUrl4: nativeAd.displayurl4, + customDisplayUrl5: nativeAd.displayurl5, + customSocialUrl1: nativeAd.socialurl1, + customSocialUrl2: nativeAd.socialurl2, + customSocialUrl3: nativeAd.socialurl3, + customSocialUrl4: nativeAd.socialurl4, + customSocialUrl5: nativeAd.socialurl5 + }; } else { Object.assign(bid, { width: rtbBid.rtb.banner.width, @@ -778,7 +856,7 @@ function bidToTag(bid) { // page.html?ast_override_div=divId:creativeId,divId2:creativeId2 const overrides = getParameterByName('ast_override_div'); if (isStr(overrides) && overrides !== '') { - const adUnitOverride = overrides.split(',').find((pair) => pair.startsWith(`${bid.adUnitCode}:`)); + const adUnitOverride = decodeURIComponent(overrides).split(',').find((pair) => pair.startsWith(`${bid.adUnitCode}:`)); if (adUnitOverride) { const forceCreativeId = adUnitOverride.split(':')[1]; if (forceCreativeId) { @@ -1256,31 +1334,29 @@ function getBidFloor(bid) { return null; } -// keywords: { 'genre': ['rock', 'pop'], 'pets': ['dog'] } goes to 'genre=rock,genre=pop,pets=dog' -function convertKeywordsToString(keywords) { - let result = ''; - Object.keys(keywords).forEach(key => { - // if 'text' or '' - if (isStr(keywords[key])) { - if (keywords[key] !== '') { - result += `${key}=${keywords[key]},` - } else { - result += `${key},`; - } - } else if (isArray(keywords[key])) { - if (keywords[key][0] === '') { - result += `${key},` - } else { - keywords[key].forEach(val => { - result += `${key}=${val},` - }); - } - } - }); +// Convert device data to a format that AppNexus expects +function convertORTB2DeviceDataToAppNexusDeviceObject(ortb2DeviceData) { + const _device = { + useragent: ortb2DeviceData.ua, + devicetype: ORTB2_DEVICE_TYPE_MAP.get(ortb2DeviceData.devicetype), + make: ortb2DeviceData.make, + model: ortb2DeviceData.model, + os: ortb2DeviceData.os, + os_version: ortb2DeviceData.osv, + w: ortb2DeviceData.w, + h: ortb2DeviceData.h, + ppi: ortb2DeviceData.ppi, + pxratio: ortb2DeviceData.pxratio, + }; - // remove last trailing comma - result = result.substring(0, result.length - 1); - return result; + // filter out any empty values and return the object + return Object.keys(_device) + .reduce((r, key) => { + if (_device[key]) { + r[key] = _device[key]; + } + return r; + }, {}); } registerBidder(spec); diff --git a/modules/appushBidAdapter.js b/modules/appushBidAdapter.js index 97772b65e45..ec742120582 100644 --- a/modules/appushBidAdapter.js +++ b/modules/appushBidAdapter.js @@ -57,6 +57,7 @@ function getPlacementReqData(bid) { placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; placement.placement = mediaTypes[VIDEO].placement; + placement.plcmt = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; @@ -84,7 +85,7 @@ function getBidFloor(bid) { mediaType: '*', size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } catch (err) { logError(err); return 0; diff --git a/modules/apstreamBidAdapter.js b/modules/apstreamBidAdapter.js index 2856fb02087..37e2bde44c1 100644 --- a/modules/apstreamBidAdapter.js +++ b/modules/apstreamBidAdapter.js @@ -292,7 +292,6 @@ function getConsentStringFromPrebid(gdprConsentConfig) { return null; } - let isIab = config.getConfig('consentManagement.cmpApi') != 'static'; let vendorConsents = ( gdprConsentConfig.vendorData.vendorConsents || (gdprConsentConfig.vendorData.vendor || {}).consents || @@ -300,7 +299,7 @@ function getConsentStringFromPrebid(gdprConsentConfig) { ); let isConsentGiven = !!vendorConsents[CONSTANTS.GVLID.toString(10)]; - return isIab && isConsentGiven ? consentString : null; + return isConsentGiven ? consentString : null; } function getIabConsentString(bidderRequest) { diff --git a/modules/arcspanRtdProvider.js b/modules/arcspanRtdProvider.js index 8ccf3d160b9..3a9e9b175d8 100644 --- a/modules/arcspanRtdProvider.js +++ b/modules/arcspanRtdProvider.js @@ -1,6 +1,7 @@ import { submodule } from '../src/hook.js'; import { mergeDeep } from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -28,7 +29,7 @@ function init(config, userConsent) { } else { scriptUrl = 'https://silo' + config.params.silo + '.p7cloud.net/as.js'; } - loadExternalScript(scriptUrl, SUBMODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME); } return true; } diff --git a/modules/asoBidAdapter.js b/modules/asoBidAdapter.js index a4a6c78566e..08612757de1 100644 --- a/modules/asoBidAdapter.js +++ b/modules/asoBidAdapter.js @@ -17,7 +17,8 @@ export const spec = { supportedMediaTypes: [BANNER, VIDEO, NATIVE], aliases: [ {code: 'bcmint'}, - {code: 'bidgency'} + {code: 'bidgency'}, + {code: 'kuantyx'} ], isBidRequestValid: bid => { @@ -106,7 +107,7 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.tagid = bidRequest.adUnitCode; - imp.secure = Number(window.location.protocol === 'https:'); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; return imp; }, diff --git a/modules/asteriobidAnalyticsAdapter.js b/modules/asteriobidAnalyticsAdapter.js index d5b6c0b4cf7..615293e2641 100644 --- a/modules/asteriobidAnalyticsAdapter.js +++ b/modules/asteriobidAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, getParameterByName, logError, logInfo, parseUrl } from '../src/utils.js' +import { generateUUID, getParameterByName, logError, logInfo, parseUrl, deepClone, hasNonSerializableProperty } from '../src/utils.js' import { ajaxBuilder } from '../src/ajax.js' import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js' import adapterManager from '../src/adapterManager.js' @@ -192,10 +192,10 @@ function handleEvent(eventType, eventArgs) { return } - try { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {} - } catch (e) { - // keep eventArgs as is + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} } const pmEvent = {} diff --git a/modules/atsAnalyticsAdapter.js b/modules/atsAnalyticsAdapter.js index d94e68b7e55..eaeead6a249 100644 --- a/modules/atsAnalyticsAdapter.js +++ b/modules/atsAnalyticsAdapter.js @@ -325,7 +325,7 @@ atsAnalyticsAdapter.getUserAgent = function () { atsAnalyticsAdapter.setSamplingCookie = function (samplRate) { const now = new Date(); - now.setTime(now.getTime() + 86400000); + now.setTime(now.getTime() + 604800000); storage.setCookie('_lr_sampling_rate', samplRate, now.toUTCString()); } diff --git a/modules/audiencerunBidAdapter.js b/modules/audiencerunBidAdapter.js index 92a4343b3ed..2e3125e8aa0 100644 --- a/modules/audiencerunBidAdapter.js +++ b/modules/audiencerunBidAdapter.js @@ -47,7 +47,7 @@ function getBidFloor(bid) { mediaType: BANNER, size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } catch (_) { return 0; } diff --git a/modules/automatadAnalyticsAdapter.js b/modules/automatadAnalyticsAdapter.js index 523e8d558ca..7ed109ab705 100644 --- a/modules/automatadAnalyticsAdapter.js +++ b/modules/automatadAnalyticsAdapter.js @@ -18,6 +18,8 @@ var isLoggingEnabled; var queuePointer = 0; var retryCount = 0; var timer = null const prettyLog = (level, text, isGroup = false, cb = () => {}) => { if (self.isLoggingEnabled === undefined) { + // TODO FIX THIS RULES VIOLATION + // eslint-disable-next-line prebid/no-global if (window.localStorage.getItem('__aggLoggingEnabled')) { self.isLoggingEnabled = true } else { diff --git a/modules/axisBidAdapter.js b/modules/axisBidAdapter.js index 8d7f2dd04fd..c2ad40b2b94 100644 --- a/modules/axisBidAdapter.js +++ b/modules/axisBidAdapter.js @@ -1,189 +1,53 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - +import { deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'axis'; const AD_URL = 'https://prebid.axis-marketplace.com/pbjs'; const SYNC_URL = 'https://cs.axis-marketplace.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { integration, token } = params; - const bidfloor = getBidFloor(bid); +const addPlacementType = (bid, bidderRequest, placement) => { + placement.integration = bid.params.integration; + placement.token = bid.params.token; +}; - const placement = { - integration, - token, - bidId, - schain, - bidfloor - }; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + const { mediaTypes } = bid; - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; + if (placement.adFormat === BANNER) { placement.pos = mediaTypes[BANNER].pos; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; + } else if (placement.adFormat === VIDEO) { placement.pos = mediaTypes[VIDEO].pos; - 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; placement.context = mediaTypes[VIDEO].context; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; } +}; - return placement; -} +const placementProcessingFunction = buildPlacementProcessingFunction({ addPlacementType, addCustomFieldsToPlacement }); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (e) { - logError(e); - return 0; - } -} + request.data.iabCat = deepAccess(bidderRequest, 'ortb2.site.cat'); + + return request; +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.integration && params.token); - - 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 = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + isBidRequestValid: isBidRequestValid(['integration', 'token'], 'every'), + buildRequests, + interpretResponse, - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - iabCat: deepAccess(bidderRequest, 'ortb2.site.cat'), - coppa: deepAccess(bidderRequest, 'ortb2.regs.coppa') ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout || 3000, - }; - - 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) => { + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; if (gdprConsent && gdprConsent.consentString) { @@ -197,6 +61,11 @@ export const spec = { syncUrl += `&ccpa=${uspConsent.consentString}`; } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncUrl += '&gpp=' + gppConsent.gppString; + syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); + } + const coppa = config.getConfig('coppa') ? 1 : 0; syncUrl += `&coppa=${coppa}`; @@ -205,6 +74,6 @@ export const spec = { url: syncUrl }]; } -}; +} registerBidder(spec); diff --git a/modules/axonixBidAdapter.js b/modules/axonixBidAdapter.js index b1ccef155de..2eefb617636 100644 --- a/modules/axonixBidAdapter.js +++ b/modules/axonixBidAdapter.js @@ -21,7 +21,7 @@ function getBidFloor(bidRequest) { }); } - return floorInfo.floor || 0; + return floorInfo?.floor || 0; } function getPageUrl(bidRequest, bidderRequest) { diff --git a/modules/azerionedgeRtdProvider.js b/modules/azerionedgeRtdProvider.js index a162ce074aa..7e8a32e6f93 100644 --- a/modules/azerionedgeRtdProvider.js +++ b/modules/azerionedgeRtdProvider.js @@ -19,6 +19,8 @@ const REAL_TIME_MODULE = 'realTimeData'; const SUBREAL_TIME_MODULE = 'azerionedge'; export const STORAGE_KEY = 'ht-pa-v1-a'; +const IMPROVEDIGITAL_GVLID = '253'; + export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBREAL_TIME_MODULE, @@ -42,14 +44,21 @@ function getScriptURL(config) { * Attach script tag to DOM * * @param {Object} config + * @param {Object} userConsent * * @return {void} */ -export function attachScript(config) { +export function attachScript(config, userConsent) { const script = getScriptURL(config); - loadExternalScript(script, SUBREAL_TIME_MODULE, () => { + loadExternalScript(script, MODULE_TYPE_RTD, SUBREAL_TIME_MODULE, () => { if (typeof window.azerionPublisherAudiences === 'function') { - window.azerionPublisherAudiences(config.params?.process || {}); + const publisherConfig = config.params?.process || {}; + window.azerionPublisherAudiences({ + ...publisherConfig, + gdprApplies: userConsent?.gdpr?.gdprApplies, + gdprConsent: userConsent?.gdpr?.consentString, + uspConsent: userConsent?.usp, + }); } }); } @@ -106,7 +115,7 @@ export function setAudiencesToBidders(reqBidsConfigObj, config, audiences) { * @return {boolean} */ function init(config, userConsent) { - attachScript(config); + attachScript(config, userConsent); return true; } @@ -138,6 +147,7 @@ export const azerionedgeSubmodule = { name: SUBREAL_TIME_MODULE, init: init, getBidRequestData: getBidRequestData, + gvlid: IMPROVEDIGITAL_GVLID, }; submodule(REAL_TIME_MODULE, azerionedgeSubmodule); diff --git a/modules/azerionedgeRtdProvider.md b/modules/azerionedgeRtdProvider.md index 6658907c480..e1bdf792647 100644 --- a/modules/azerionedgeRtdProvider.md +++ b/modules/azerionedgeRtdProvider.md @@ -79,23 +79,6 @@ provided to the module when the user gives the relevant permissions on the publi As Prebid.js utilizes TCF vendor consent for the RTD module to load, the module needs to be labeled within the Vendor Exceptions. -### Instructions - -If the Prebid GDPR enforcement is enabled, the module should be labeled -as exception, as shown below: - -```js -[ - { - purpose: 'storage', - enforcePurpose: true, - enforceVendor: true, - vendorExceptions: ["azerionedge"] - }, - ... -] -``` - ## Testing To view an example: diff --git a/modules/beachfrontBidAdapter.js b/modules/beachfrontBidAdapter.js index 658fc30b43b..d05769de010 100644 --- a/modules/beachfrontBidAdapter.js +++ b/modules/beachfrontBidAdapter.js @@ -4,18 +4,16 @@ import { deepSetValue, getUniqueIdentifierStr, isArray, - isFn, logWarn, - parseSizesInput, - parseUrl, formatQS } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {Renderer} from '../src/Renderer.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; +import {find} from '../src/polyfill.js'; +import { getFirstSize, getOsVersion, getVideoSizes, getBannerSizes, isConnectedTV, getDoNotTrack, isMobile, isBannerBid, isVideoBid, getBannerBidFloor, getVideoBidFloor, getVideoTargetingParams, getTopWindowLocation } from '../libraries/advangUtils/index.js'; -const ADAPTER_VERSION = '1.20'; +const ADAPTER_VERSION = '1.21'; const ADAPTER_NAME = 'BFIO_PREBID'; const OUTSTREAM = 'outstream'; const CURRENCY = 'USD'; @@ -26,7 +24,7 @@ export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/l export const SYNC_IFRAME_ENDPOINT = 'https://sync.bfmio.com/sync_iframe'; export const SYNC_IMAGE_ENDPOINT = 'https://sync.bfmio.com/syncb'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'skip', 'skipmin', 'skipafter']; +export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'placement', 'plcmt', 'skip', 'skipmin', 'skipafter']; export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; export const SUPPORTED_USER_IDS = [ @@ -222,69 +220,6 @@ function createRenderer(bidRequest) { return renderer; } -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function 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 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 isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - function getVideoBidParam(bid, key) { return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); } @@ -298,16 +233,6 @@ function getPlayerBidParam(bid, key, defaultValue) { return param === undefined ? defaultValue : param; } -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: CURRENCY, mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - function isVideoBidValid(bid) { return isVideoBid(bid) && getVideoBidParam(bid, 'appId') && getVideoBidParam(bid, 'bidfloor'); } @@ -316,10 +241,6 @@ function isBannerBidValid(bid) { return isBannerBid(bid) && getBannerBidParam(bid, 'appId') && getBannerBidParam(bid, 'bidfloor'); } -function getTopWindowLocation(bidderRequest) { - return parseUrl(bidderRequest?.refererInfo?.page, { decodeSearchAsString: true }); -} - function getEids(bid) { return SUPPORTED_USER_IDS .map(getUserId(bid)) @@ -347,26 +268,10 @@ function formatEid(id, source, rtiPartner, atype) { }; } -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - function createVideoRequestData(bid, bidderRequest) { let sizes = getVideoSizes(bid); let firstSize = getFirstSize(sizes); - let video = getVideoTargetingParams(bid); + let video = getVideoTargetingParams(bid, VIDEO_TARGETING); let appId = getVideoBidParam(bid, 'appId'); let bidfloor = getVideoBidFloor(bid); let tagid = getVideoBidParam(bid, 'tagid'); diff --git a/modules/beopBidAdapter.js b/modules/beopBidAdapter.js index 0b2a965448b..5b9e57628c6 100644 --- a/modules/beopBidAdapter.js +++ b/modules/beopBidAdapter.js @@ -1,14 +1,16 @@ import { buildUrl, - deepAccess, getBidIdParameter, + deepAccess, generateUUID, getBidIdParameter, getValue, isArray, + isPlainObject, logInfo, logWarn, triggerPixel } from '../src/utils.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; import {config} from '../src/config.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; @@ -20,9 +22,11 @@ import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; const BIDDER_CODE = 'beop'; const ENDPOINT_URL = 'https://hb.beop.io/bid'; +const COOKIE_NAME = 'beopid'; const TCF_VENDOR_ID = 666; const validIdRegExp = /^[0-9a-fA-F]{24}$/ +const storage = getStorageManager({bidderCode: BIDDER_CODE}); export const spec = { code: BIDDER_CODE, @@ -63,17 +67,30 @@ export const spec = { const kwdsFromRequest = firstSlot.kwds; let keywords = getAllOrtbKeywords(bidderRequest.ortb2, kwdsFromRequest); + let beopid = ''; + if (storage.cookiesAreEnabled) { + beopid = storage.getCookie(COOKIE_NAME, undefined); + if (!beopid) { + beopid = generateUUID(); + let expirationDate = new Date(); + expirationDate.setTime(expirationDate.getTime() + 86400 * 183 * 1000); + storage.setCookie(COOKIE_NAME, beopid, expirationDate.toUTCString()); + } + } else { + storage.setCookie(COOKIE_NAME, '', 0); + } + const payloadObject = { at: new Date().toString(), nid: firstSlot.nid, nptnid: firstSlot.nptnid, pid: firstSlot.pid, - psegs: psegs, - bpsegs: (userBpSegs.concat(siteBpSegs)).map(item => item.toString()), + bpsegs: (userBpSegs.concat(siteBpSegs, psegs)).map(item => item.toString()), url: pageUrl, lang: (window.navigator.language || window.navigator.languages[0]), kwds: keywords, dbg: false, + fg: beopid, slts: slots, is_amp: deepAccess(bidderRequest, 'referrerInfo.isAmp'), gdpr_applies: gdpr ? gdpr.gdprApplies : false, @@ -148,7 +165,7 @@ function beOpRequestSlotsMaker(bid) { 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))) { + if (isPlainObject(floorInfo) && floorInfo.currency === publisherCurrency && !isNaN(parseFloat(floorInfo.floor))) { floor = parseFloat(floorInfo.floor); } } diff --git a/modules/beyondmediaBidAdapter.js b/modules/beyondmediaBidAdapter.js index bbcd972470c..077717c36f4 100644 --- a/modules/beyondmediaBidAdapter.js +++ b/modules/beyondmediaBidAdapter.js @@ -1,202 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'beyondmedia'; const AD_URL = 'https://backend.andbeyond.media/pbjs'; const SYNC_URL = 'https://cookies.andbeyond.media'; -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 = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.placementId); - - 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]); - } - - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/bidResponseFilter/index.js b/modules/bidResponseFilter/index.js new file mode 100644 index 00000000000..5b138965983 --- /dev/null +++ b/modules/bidResponseFilter/index.js @@ -0,0 +1,55 @@ +import { auctionManager } from '../../src/auctionManager.js'; +import { config } from '../../src/config.js'; +import { getHook } from '../../src/hook.js'; + +export const MODULE_NAME = 'bidResponseFilter'; +export const BID_CATEGORY_REJECTION_REASON = 'Category is not allowed'; +export const BID_ADV_DOMAINS_REJECTION_REASON = 'Adv domain is not allowed'; +export const BID_ATTR_REJECTION_REASON = 'Attr is not allowed'; + +let moduleConfig; +let enabled = false; + +function init() { + config.getConfig(MODULE_NAME, (cfg) => { + moduleConfig = cfg[MODULE_NAME]; + if (enabled && !moduleConfig) { + reset(); + } else if (!enabled && moduleConfig) { + enabled = true; + getHook('addBidResponse').before(addBidResponseHook); + } + }) +} + +export function reset() { + enabled = false; + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); +} + +export function addBidResponseHook(next, adUnitCode, bid, reject, index = auctionManager.index) { + const {bcat = [], badv = []} = index.getOrtb2(bid) || {}; + const battr = index.getBidRequest(bid)?.ortb2Imp[bid.mediaType]?.battr || index.getAdUnit(bid)?.ortb2Imp[bid.mediaType]?.battr || []; + + const catConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.cat || {})}; + const advConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.adv || {})}; + const attrConfig = {enforce: true, blockUnknown: true, ...(moduleConfig?.attr || {})}; + + const { primaryCatId, secondaryCatIds = [], advertiserDomains = [], attr: metaAttr } = bid.meta || {}; + + // checking if bid fulfills ortb2 fields rules + if ((catConfig.enforce && bcat.some(category => [primaryCatId, ...secondaryCatIds].includes(category))) || + (catConfig.blockUnknown && !primaryCatId)) { + reject(BID_CATEGORY_REJECTION_REASON); + } else if ((advConfig.enforce && badv.some(domain => advertiserDomains.includes(domain))) || + (advConfig.blockUnknown && !advertiserDomains.length)) { + reject(BID_ADV_DOMAINS_REJECTION_REASON); + } else if ((attrConfig.enforce && battr.includes(metaAttr)) || + (attrConfig.blockUnknown && !metaAttr)) { + reject(BID_ATTR_REJECTION_REASON); + } else { + return next(adUnitCode, bid, reject); + } +} + +init(); diff --git a/modules/bidViewability.js b/modules/bidViewability.js index c1aab83b7e6..3bfcd93174a 100644 --- a/modules/bidViewability.js +++ b/modules/bidViewability.js @@ -4,11 +4,12 @@ import {config} from '../src/config.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import {isFn, logWarn, triggerPixel} from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import adapterManager, {gdprDataHandler, uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; +import adapterManager, {gppDataHandler, uspDataHandler} from '../src/adapterManager.js'; import {find} from '../src/polyfill.js'; +import {gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; const MODULE_NAME = 'bidViewability'; const CONFIG_ENABLED = 'enabled'; @@ -32,14 +33,7 @@ export let getMatchingWinningBidForGPTSlot = (globalModuleConfig, slot) => { 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; } - } + let queryParams = gdprParams(); const uspConsent = uspDataHandler.getConsentData(); if (uspConsent) { queryParams.us_privacy = uspConsent; } @@ -67,7 +61,6 @@ export let logWinningBidNotFound = (slot) => { export let impressionViewableHandler = (globalModuleConfig, slot, event) => { let respectiveBid = getMatchingWinningBidForGPTSlot(globalModuleConfig, slot); - let respectiveDeferredAdUnit = getGlobal().adUnits.find(adUnit => adUnit.deferBilling && respectiveBid.adUnitCode === adUnit.code); if (respectiveBid === null) { logWinningBidNotFound(slot); @@ -77,8 +70,8 @@ export let impressionViewableHandler = (globalModuleConfig, slot, event) => { // trigger respective bidder's onBidViewable handler adapterManager.callBidViewableBidder(respectiveBid.adapterCode || respectiveBid.bidder, respectiveBid); - if (respectiveDeferredAdUnit) { - adapterManager.callBidBillableBidder(respectiveBid); + if (respectiveBid.deferBilling) { + adapterManager.triggerBilling(respectiveBid); } // emit the BID_VIEWABLE event with bid details, this event can be consumed by bidders and analytics pixels diff --git a/modules/biddoBidAdapter.js b/modules/biddoBidAdapter.js index cf39c572629..6bfa0ac6ef8 100644 --- a/modules/biddoBidAdapter.js +++ b/modules/biddoBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +import { buildBannerRequests, interpretBannerResponse } from '../libraries/biddoInvamiaUtils/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,86 +13,15 @@ const ENDPOINT_URL = 'https://ad.adopx.net/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) { + 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; + buildRequests: function (validBidRequests) { + return validBidRequests.flatMap((bidRequest) => buildBannerRequests(bidRequest, ENDPOINT_URL)); }, - /** - * 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; + interpretResponse: function (serverResponse, { bidderRequest }) { + return interpretBannerResponse(serverResponse, bidderRequest); }, -} +}; registerBidder(spec); diff --git a/modules/bidmaticBidAdapter.js b/modules/bidmaticBidAdapter.js new file mode 100644 index 00000000000..8c22d70c632 --- /dev/null +++ b/modules/bidmaticBidAdapter.js @@ -0,0 +1,110 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { replaceAuctionPrice, isNumber, deepAccess, isFn } from '../src/utils.js'; + +export const END_POINT = 'https://adapter.bidmatic.io/ortb-client'; +const BIDDER_CODE = 'bidmatic'; +const DEFAULT_CURRENCY = 'USD'; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 290, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const floorInfo = isFn(bidRequest.getFloor) ? bidRequest.getFloor({ + currency: context.currency || 'USD', + size: '*', + mediaType: '*' + }) : { + floor: imp.bidfloor || deepAccess(bidRequest, 'params.bidfloor') || 0, + currency: DEFAULT_CURRENCY + }; + + if (floorInfo) { + imp.bidfloor = floorInfo.floor; + imp.bidfloorcur = floorInfo.currency; + } + imp.tagid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || bidRequest.adUnitCode; + + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + if (!request.cur) { + request.cur = [DEFAULT_CURRENCY]; + } + return request; + }, + bidResponse(buildBidResponse, bid, context) { + const { bidRequest } = context; + + let resMediaType; + const reqMediaTypes = Object.keys(bidRequest.mediaTypes); + if (reqMediaTypes.length === 1) { + resMediaType = reqMediaTypes[0]; + } else { + if (bid.adm.search(/^(<\?xml| { + acc[bidRequest.params.source] = acc[bidRequest.params.source] || []; + acc[bidRequest.params.source].push(bidRequest); + return acc; + }, {}); + + return Object.entries(requestsBySource).map(([source, bidRequests]) => { + const data = converter.toORTB({ bidRequests, bidderRequest }); + const url = new URL(END_POINT); + url.searchParams.append('source', source); + return { + method: 'POST', + url: url.toString(), + data: data, + options: { + withCredentials: true, + } + }; + }); + }, + + interpretResponse: function (serverResponse, bidRequest) { + if (!serverResponse || !serverResponse.body) return []; + const parsedSeatbid = serverResponse.body.seatbid.map(seatbidItem => { + const parsedBid = seatbidItem.bid.map((bidItem) => ({ + ...bidItem, + adm: replaceAuctionPrice(bidItem.adm, bidItem.price), + nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price) + })); + return { ...seatbidItem, bid: parsedBid }; + }); + const responseBody = { ...serverResponse.body, seatbid: parsedSeatbid }; + return converter.fromORTB({ + response: responseBody, + request: bidRequest.data, + }).bids; + }, + +}; +registerBidder(spec); diff --git a/modules/bidmaticBidAdapter.md b/modules/bidmaticBidAdapter.md new file mode 100644 index 00000000000..d248b386ea3 --- /dev/null +++ b/modules/bidmaticBidAdapter.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Bidmatic Bid Adapter +Module Type: Bidder Adapter +Maintainer: mg@bidmatic.io +``` + +# Description + +Adds access to Bidmatic SSP oRTB service. + +# Sample Ad Unit: For Publishers +``` +var adUnits = [{ + code: 'bg-test-rectangle', + sizes: [[300, 250]], + bids: [{ + bidder: 'bidmatic', + params: { + source: 886409, + bidfloor: 0.1 + } + }] +}] +``` diff --git a/modules/bidtheatreBidAdapter.js b/modules/bidtheatreBidAdapter.js new file mode 100644 index 00000000000..8fb3dc2fd3b --- /dev/null +++ b/modules/bidtheatreBidAdapter.js @@ -0,0 +1,117 @@ +import { ortbConverter } from '../libraries/ortbConverter/converter.js' +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { deepSetValue, logError, replaceAuctionPrice } from '../src/utils.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const GVLID = 30; +export const BIDDER_CODE = 'bidtheatre'; +export const ENDPOINT_URL = 'https://prebidjs-bids.bidtheatre.net/prebidjsbid'; +const METHOD = 'POST'; +const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO]; +export const DEFAULT_CURRENCY = 'USD'; +const BIDTHEATRE_COOKIE_NAME = '__kuid'; +const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 120, + currency: DEFAULT_CURRENCY + } +}); + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + gvlid: GVLID, + isBidRequestValid: function (bidRequest) { + const isValid = bidRequest && + bidRequest.params && + typeof bidRequest.params.publisherId === 'string' && + bidRequest.params.publisherId.trim().length === 36 + + if (!isValid) { + logError('Bidtheatre Header Bidding Publisher ID not provided or in incorrect format'); + } + + return isValid; + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const seenUrls = new Set(); + const syncs = []; + + if (syncOptions.pixelEnabled && serverResponses) { + serverResponses.forEach(response => { + if (response.body && response.body.seatbid) { + response.body.seatbid.forEach(seatbid => { + if (seatbid.bid) { + seatbid.bid.forEach(bid => { + const urls = bid.ext && bid.ext.usersync_urls; + if (Array.isArray(urls)) { + urls.forEach(url => { + if (!seenUrls.has(url)) { + seenUrls.add(url); + syncs.push({ + type: 'image', + url: url + }); + } + }); + } + }); + } + }); + } + }); + } + return syncs; + }, + buildRequests(bidRequests, bidderRequest) { + const data = converter.toORTB({bidRequests, bidderRequest}); + + const cookieValue = storage.getCookie(BIDTHEATRE_COOKIE_NAME); + if (cookieValue) { + deepSetValue(data, 'user.buyeruid', cookieValue); + } + + data.imp.forEach((impObj, index) => { + let publisherId = bidRequests[index].params.publisherId; + + if (publisherId) { + deepSetValue(impObj, 'ext.bidder.publisherId', publisherId); + } + }); + + return [{ + method: METHOD, + url: ENDPOINT_URL, + data + }] + }, + interpretResponse(response, request) { + if (!response || !response.body || !response.body.seatbid) { + return []; + } + + const macroReplacedSeatbid = response.body.seatbid.map(seatbidItem => { + const macroReplacedBid = seatbidItem.bid.map((bidItem) => ({ + ...bidItem, + adm: replaceAuctionPrice(bidItem.adm, bidItem.price), + nurl: replaceAuctionPrice(bidItem.nurl, bidItem.price) + })); + return { ...seatbidItem, bid: macroReplacedBid }; + }); + + const macroReplacedResponseBody = { ...response.body, seatbid: macroReplacedSeatbid }; + const bids = converter.fromORTB({response: macroReplacedResponseBody, request: request.data}).bids; + return bids; + }, + onTimeout: function(timeoutData) {}, + onBidWon: function(bid) {}, + onSetTargeting: function(bid) {}, + // onBidderError: function({ error, bidderRequest }) {}, + onAdRenderSucceeded: function(bid) {} +} + +registerBidder(spec); diff --git a/modules/bidtheatreBidAdapter.md b/modules/bidtheatreBidAdapter.md new file mode 100644 index 00000000000..7f9301596aa --- /dev/null +++ b/modules/bidtheatreBidAdapter.md @@ -0,0 +1,111 @@ +# Overview + +``` +Module Name : Bidtheatre Bidder Adapter +Module Type : Bidder Adapter +Maintainer : operations@bidtheatre.com +``` + +# Description + +Module that connects to Bidtheatre's demand sources + +About us: https://www.bidtheatre.com + +The Bidtheatre Bidding adapter requires manual set up before use. Please contact us at [operations@bidtheatre.com](mailto:operations@bidtheatre.com) if you would like to access Bidtheatre demand. + +# Bid params +| Name | Scope | Description | Example | +|:--------------| :------- |:---------------------------------------|:-------------------------------------| +| `publisherId` | required | Manually set up publisher ID | `73b20b3a-12a0-4869-b54e-8d42b55786ee`| + +In addition to the required bid param above, Bidtheatre will also enforce the following requirements +- All ad slots on a page must belong to the same publisher ID +- The publisher must provide either a client IP and/or explicit geo data in the request + +# Test Parameters + +## Banner + +```javascript +var displayAdUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[980,240]] + } + }, + bids: [ + { + bidder: 'bidtheatre', + params: { + publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee' + } + } + ] + } +]; +``` + +## Video + +```javascript +var videoAdUnits = [ + { + code: 'test-video', + mediaTypes: { + video: { + playerSize: [[1280, 720]] + } + }, + bids: [ + { + bidder: 'bidtheatre', + params: { + publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee' + } + } + ] + } +]; +``` + +## Multiformat + +```javascript +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[980,240]] + } + }, + bids: [ + { + bidder: 'bidtheatre', + params: { + publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee' + } + } + ] + }, + { + code: 'test-video', + mediaTypes: { + video: { + playerSize: [[1280, 720]] + } + }, + bids: [ + { + bidder: 'bidtheatre', + params: { + publisherId: '73b20b3a-12a0-4869-b54e-8d42b55786ee' + } + } + ] + } +]; +``` diff --git a/modules/bidwatchAnalyticsAdapter.js b/modules/bidwatchAnalyticsAdapter.js index ffbd125eeab..e385b02fe5f 100644 --- a/modules/bidwatchAnalyticsAdapter.js +++ b/modules/bidwatchAnalyticsAdapter.js @@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { deepClone } from '../src/utils.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -113,7 +114,7 @@ function addTimeout(args) { let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); argsDereferenced = stringArgs; argsDereferenced.forEach((attr) => { - argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + argsCleaned.push(filterAttributes(deepClone(attr), false)); }); if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } auctionEnd[eventType].push(argsCleaned); diff --git a/modules/bizzclickBidAdapter.js b/modules/blastoBidAdapter.js similarity index 90% rename from modules/bizzclickBidAdapter.js rename to modules/blastoBidAdapter.js index d2eba3f0f81..0e97c294049 100644 --- a/modules/bizzclickBidAdapter.js +++ b/modules/blastoBidAdapter.js @@ -3,11 +3,11 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -const BIDDER_CODE = 'bizzclick'; +const BIDDER_CODE = 'blasto'; const SOURCE_ID_MACRO = '[sourceid]'; const ACCOUNT_ID_MACRO = '[accountid]'; const HOST_MACRO = '[host]'; -const URL = `https://${HOST_MACRO}.bizzclick.com/bid?rtb_seat_id=${SOURCE_ID_MACRO}&secret_key=${ACCOUNT_ID_MACRO}&integration_type=prebidjs`; +const URL = `https://${HOST_MACRO}.blasto.ai/bid?rtb_seat_id=${SOURCE_ID_MACRO}&secret_key=${ACCOUNT_ID_MACRO}&integration_type=prebidjs`; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_HOST = 'us-e-node1'; @@ -53,7 +53,7 @@ export const spec = { buildRequests: (validBidRequests, bidderRequest) => { if (validBidRequests && validBidRequests.length === 0) return []; const { sourceId, accountId } = validBidRequests[0].params; - const host = validBidRequests[0].params.host || 'USE'; + const host = validBidRequests[0].params.host; const endpointURL = URL.replace(HOST_MACRO, host || DEFAULT_HOST) .replace(ACCOUNT_ID_MACRO, accountId) .replace(SOURCE_ID_MACRO, sourceId); diff --git a/modules/bizzclickBidAdapter.md b/modules/blastoBidAdapter.md similarity index 88% rename from modules/bizzclickBidAdapter.md rename to modules/blastoBidAdapter.md index ad342f34e07..60ebad14764 100644 --- a/modules/bizzclickBidAdapter.md +++ b/modules/blastoBidAdapter.md @@ -1,14 +1,14 @@ # Overview ``` -Module Name: BizzClick SSP Bidder Adapter +Module Name: Blasto SSP Bidder Adapter Module Type: Bidder Adapter -Maintainer: support@bizzclick.com +Maintainer: support@blasto.ai ``` # Description -Module that connects to BizzClick SSP demand sources +Module that connects to Blasto SSP demand sources # Test Parameters @@ -26,7 +26,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", @@ -68,7 +68,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", @@ -96,7 +96,7 @@ const adUnits = [ }, bids: [ { - bidder: "bizzclick", + bidder: "blasto", params: { placementId: "hash", accountId: "accountId", diff --git a/modules/bliinkBidAdapter.js b/modules/bliinkBidAdapter.js index 37c99878d68..b66923fd476 100644 --- a/modules/bliinkBidAdapter.js +++ b/modules/bliinkBidAdapter.js @@ -1,8 +1,6 @@ -// eslint-disable-next-line prebid/validate-imports -// eslint-disable-next-line prebid/validate-imports import { registerBidder } from '../src/adapters/bidderFactory.js' import { config } from '../src/config.js' -import { _each, deepAccess, deepSetValue, getWindowSelf, getWindowTop } from '../src/utils.js' +import { _each, canAccessWindowTop, deepAccess, deepSetValue, getDomLoadingDuration, getWindowSelf, getWindowTop } from '../src/utils.js' export const BIDDER_CODE = 'bliink' export const GVL_ID = 658 export const BLIINK_ENDPOINT_ENGINE = 'https://engine.bliink.io/prebid' @@ -123,35 +121,6 @@ export function getKeywords() { return []; } -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -} - -/** - * domLoading feature is computed on window.top if reachable. - */ -export function getDomLoadingDuration() { - let domLoadingDuration = -1; - let performance; - - performance = (canAccessTopWindow()) ? getWindowTop().performance : getWindowSelf().performance; - - if (performance && performance.timing && performance.timing.navigationStart > 0) { - const val = performance.timing.domLoading - performance.timing.navigationStart; - if (val > 0) { - domLoadingDuration = val; - } - } - - return domLoadingDuration; -} - /** * @param bidResponse * @return {({cpm, netRevenue: boolean, requestId, width: number, currency, ttl: number, creativeId, height: number}&{mediaType: string, vastXml})|null} @@ -210,7 +179,8 @@ export const isBidRequestValid = (bid) => { */ export const buildRequests = (validBidRequests, bidderRequest) => { if (!validBidRequests || !bidderRequest || !bidderRequest.bids) return null - const domLoadingDuration = getDomLoadingDuration().toString(); + const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); + const domLoadingDuration = getDomLoadingDuration(w).toString(); const tags = bidderRequest.bids.map((bid) => { let bidFloor; const sizes = bid.sizes.map((size) => ({ w: size[0], h: size[1] })); diff --git a/modules/bluebillywigBidAdapter.js b/modules/bluebillywigBidAdapter.js deleted file mode 100644 index 0718f512cdd..00000000000 --- a/modules/bluebillywigBidAdapter.js +++ /dev/null @@ -1,374 +0,0 @@ -import {deepAccess, deepClone, deepSetValue, logError, logWarn} from '../src/utils.js'; -import {find} from '../src/polyfill.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {Renderer} from '../src/Renderer.js'; - -const DEV_MODE = window.location.search.match(/bbpbs_debug=true/); - -// Blue Billywig Constants -const BB_CONSTANTS = { - BIDDER_CODE: 'bluebillywig', - AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', - SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', - RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', - DEFAULT_TIMEOUT: 5000, - DEFAULT_TTL: 300, - DEFAULT_WIDTH: 768, - DEFAULT_HEIGHT: 432, - 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 -const BB_HELPERS = { - addSiteAppDevice: function(request, pageUrl) { - if (typeof getConfig('app') === 'object') request.app = getConfig('app'); - else { - request.site = {}; - if (typeof getConfig('site') === 'object') request.site = getConfig('site'); - if (pageUrl) request.site.page = pageUrl; - } - - if (typeof getConfig('device') === 'object') request.device = getConfig('device'); - if (!request.device) request.device = {}; - if (!request.device.w) request.device.w = window.innerWidth; - if (!request.device.h) request.device.h = window.innerHeight; - }, - addSchain: function(request, validBidRequests) { - const schain = deepAccess(validBidRequests, '0.schain'); - if (schain) request.source.ext = { schain: schain }; - }, - addCurrency: function(request) { - 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) { - const eids = deepAccess(validBidRequests, '0.userIdAsEids'); - - if (eids != null && eids.length) { - deepSetValue(request, 'user.ext.eids', eids); - } - }, - substituteUrl: function (url, publication, renderer) { - return url.replace('$$URL_START', (DEV_MODE) ? 'https://dev.' : 'https://').replace('$$PUBLICATION', publication).replace('$$RENDERER', renderer); - }, - getAuctionUrl: function(publication) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.AUCTION_URL, publication); - }, - getSyncUrl: function(publication) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.SYNC_URL, publication); - }, - getRendererUrl: function(publication, renderer) { - return BB_HELPERS.substituteUrl(BB_CONSTANTS.RENDERER_URL, publication, renderer); - }, - transformVideoParams: function(videoParams, videoParamsExt) { - videoParams = deepClone(videoParams); - - 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; - - 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) { - 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 = deepAccess(bid, 'ext.prebid.targeting'); - const extPrebidCache = deepAccess(bid, 'ext.prebid.cache'); - - if (extPrebidCache && typeof extPrebidCache.vastXml === 'object' && extPrebidCache.vastXml.cacheId && extPrebidCache.vastXml.url) { - bidObject.videoCacheKey = extPrebidCache.vastXml.cacheId; - 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) { - bidObject.ad = bid.adm; - bidObject.vastXml = bid.adm; - } - if (!bidObject.vastUrl && bid.nurl && !bid.adm) { // ad markup is on win notice url, and adm is ommited according to OpenRTB 2.5 - bidObject.vastUrl = bid.nurl; - } - bidObject.meta = bid.meta || {}; - if (bid.adomain) { bidObject.meta.advertiserDomains = bid.adomain; } - return bidObject; - }, -}; - -// Renderer Functions -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) { - logWarn(`${BB_CONSTANTS.BIDDER_CODE}: No vastXml or vastUrl on bid, bailing...`); - return; - } - - if (!(window.bluebillywig && window.bluebillywig.renderers)) { - 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); - - if (renderer) renderer.bootstrap(config, ele, bid.rendererSettings || {}); - else logWarn(`${BB_CONSTANTS.BIDDER_CODE}: Couldn't find a renderer with ${rendererId}`); - }, - newRenderer: function(rendererUrl, adUnitCode) { - const renderer = Renderer.install({ - url: rendererUrl, - loaded: false, - adUnitCode - }); - - try { - renderer.setRender(BB_RENDERER.outstreamRender); - } catch (err) { - logWarn(`${BB_CONSTANTS.BIDDER_CODE}: 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! - } -}; - -// Spec Functions -// These functions are used to construct the core spec for the adapter -export const spec = { - code: BB_CONSTANTS.BIDDER_CODE, - supportedMediaTypes: [VIDEO], - syncStore: { bidders: [], }, - isBidRequestValid(bid) { - const publicationNameRegex = /^\w+\.?\w+$/; - const rendererRegex = /^[\w+_]+$/; - - if (!bid.params) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no params set on bid. Rejecting bid: `, bid); - return false; - } - - if (!bid.params.hasOwnProperty('publicationName') || typeof bid.params.publicationName !== 'string') { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no publicationName specified in bid params, or it's not a string. Rejecting bid: `, bid); - return false; - } else if (!publicationNameRegex.test(bid.params.publicationName)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: publicationName must be in format 'publication' or 'publication.environment'. Rejecting bid: `, bid); - return false; - } - - if ((!bid.params.hasOwnProperty('rendererCode') || typeof bid.params.rendererCode !== 'string')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no rendererCode was specified in bid params. Rejecting bid: `, bid); - return false; - } else if (!rendererRegex.test(bid.params.rendererCode)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: rendererCode must be alphanumeric, including underscores. Rejecting bid: `, bid); - return false; - } - - if (!bid.params.accountId) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no accountId specified in bid params. Rejecting bid: `, bid); - return false; - } - - if (bid.params.hasOwnProperty('connections')) { - if (!Array.isArray(bid.params.connections)) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: connections is not of type array. Rejecting bid: `, bid); - return false; - } else { - for (let i = 0; i < bid.params.connections.length; i++) { - if (!bid.params.hasOwnProperty(bid.params.connections[i])) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: connection specified in params.connections, but not configured in params. Rejecting bid: `, bid); - return false; - } - } - } - } else { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no connections specified in bid. Rejecting bid: `, bid); - return false; - } - - if (bid.params.hasOwnProperty('video') && (bid.params.video === null || typeof bid.params.video !== 'object')) { - 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')) { - 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')) { - logError(`${BB_CONSTANTS.BIDDER_CODE}: no context specified in bid. Rejecting bid: `, bid); - return false; - } - - if (bid.mediaTypes[VIDEO].context !== 'outstream') { - logError(`${BB_CONSTANTS.BIDDER_CODE}: video.context is invalid, must be "outstream". Rejecting bid: `, bid); - return false; - } - } else { - logError(`${BB_CONSTANTS.BIDDER_CODE}: mediaTypes or mediaTypes.video is not specified. Rejecting bid: `, bid); - return false; - } - - return true; - }, - buildRequests(validBidRequests, bidderRequest) { - const imps = []; - - 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((extBuilder, connection) => { - extBuilder[connection] = validBidRequest.params[connection]; - - if (this.syncStore.bidders.indexOf(connection) === -1) this.syncStore.bidders.push(connection); - - return extBuilder; - }, {}); - - const videoParams = BB_HELPERS.transformVideoParams(deepAccess(validBidRequest, 'mediaTypes.video'), deepAccess(validBidRequest, 'params.video')); - imps.push({ id: validBidRequest.bidId, ext, secure: window.location.protocol === 'https' ? 1 : 0, video: videoParams }); - }); - - const request = { - id: bidderRequest.bidderRequestId, - source: {tid: bidderRequest.ortb2?.source?.tid}, - tmax: Math.min(BB_CONSTANTS.DEFAULT_TIMEOUT, bidderRequest.timeout), - imp: imps, - test: DEV_MODE ? 1 : 0, - ext: { - prebid: { - targeting: { includewinners: true, includebidderkeys: false } - } - } - }; - - // handle privacy settings for GDPR/CCPA/COPPA - if (bidderRequest.gdprConsent) { - let gdprApplies = 0; - if (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') gdprApplies = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - this.syncStore.uspConsent = bidderRequest.uspConsent; - } - - if (getConfig('coppa') == true) deepSetValue(request, 'regs.coppa', 1); - - // Enrich the request with any external data we may have - BB_HELPERS.addSiteAppDevice(request, bidderRequest.refererInfo && bidderRequest.refererInfo.page); - BB_HELPERS.addSchain(request, validBidRequests); - BB_HELPERS.addCurrency(request); - BB_HELPERS.addUserIds(request, validBidRequests); - - return { - method: 'POST', - url: BB_HELPERS.getAuctionUrl(validBidRequests[0].params.publicationName), - data: JSON.stringify(request), - bidderRequest: bidderRequest - }; - }, - interpretResponse(serverResponse, request) { - serverResponse = serverResponse.body || {}; - - if (!serverResponse.hasOwnProperty('seatbid') || !Array.isArray(serverResponse.seatbid)) { - return []; - } - - const bids = []; - - serverResponse.seatbid.forEach(seatbid => { - if (!seatbid.bid || !Array.isArray(seatbid.bid)) return; - seatbid.bid.forEach(bid => { - bid = BB_HELPERS.transformRTBToPrebidProps(bid, serverResponse); - - 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 (!syncOptions.iframeEnabled) return []; - - const queryString = []; - - 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=${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(this.syncStore.publicationName)}&${queryString.join('&')}`; - - return [{ - type: 'iframe', - url: syncUrl - }]; - } -}; - -registerBidder(spec); diff --git a/modules/bluebillywigBidAdapter.md b/modules/bluebillywigBidAdapter.md deleted file mode 100644 index 7879697baf5..00000000000 --- a/modules/bluebillywigBidAdapter.md +++ /dev/null @@ -1,38 +0,0 @@ -# Overview - -``` -Module Name: Blue Billywig Adapter -Module Type: Bidder Adapter -Maintainer: dev+prebid@bluebillywig.com -``` - -# Description - -Prebid Blue Billywig Bidder Adapter - -# Test Parameters - -``` - const adUnits = [{ - code: 'ad-unit', - sizes: [[[768,432],[640,480],[640,360]]], - mediaTypes: { - video: { - playerSize: [768, 432], - context: 'outstream', - mimes: ['video/mp4'], - protocols: [ 2,3,5,6] - } - }, - bids: [{ - bidder: 'bluebillywig', - params: { - publicationName: "bbprebid", - rendererCode: "renderer", - accountId: 642, - connections: [ 'bluebillywig' ], - bluebillywig: {} - } - }] - }]; -``` diff --git a/modules/boldwinBidAdapter.js b/modules/boldwinBidAdapter.js index c7def383b5e..1cf3bf889b7 100644 --- a/modules/boldwinBidAdapter.js +++ b/modules/boldwinBidAdapter.js @@ -1,175 +1,38 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'boldwin'; const AD_URL = 'https://ssp.videowalldirect.com/pbjs'; const SYNC_URL = 'https://sync.videowalldirect.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } - 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; - } -} +}; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && (bid.params.placementId || bid.params.endpointId)); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - const { mediaTypes, params } = 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]; - } - } - - const { placementId, endpointId } = params; - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - placements.push({ - ...placement, - bidId: bid.bidId, - sizes: sizes || [], - wPlayer: sizes ? sizes[0] : 0, - hPlayer: sizes ? sizes[1] : 0, - schain: bid.schain || {}, - bidFloor: getBidFloor(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: () => { - return [{ - type: 'image', - url: SYNC_URL - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/brandmetricsRtdProvider.js b/modules/brandmetricsRtdProvider.js index 1c96fe3915d..8ecaeb9ea4c 100644 --- a/modules/brandmetricsRtdProvider.js +++ b/modules/brandmetricsRtdProvider.js @@ -10,6 +10,7 @@ import { deepAccess, deepSetValue, logError, mergeDeep, generateUUID } from '../ import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -144,7 +145,7 @@ function initializeBrandmetrics(scriptId) { const file = scriptId + '.js' const url = path + file - loadExternalScript(url, MODULE_CODE) + loadExternalScript(url, MODULE_TYPE_RTD, MODULE_CODE) } } diff --git a/modules/braveBidAdapter.js b/modules/braveBidAdapter.js index 4c5448482db..7689aade114 100644 --- a/modules/braveBidAdapter.js +++ b/modules/braveBidAdapter.js @@ -1,8 +1,8 @@ -import {isEmpty, isStr, parseUrl, triggerPixel} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import { isStr, triggerPixel } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { parseNative } from '../libraries/braveUtils/index.js'; +import { buildRequests, interpretResponse } from '../libraries/braveUtils/buildAndInterpret.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -13,160 +13,15 @@ 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) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - 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 = { - // TODO: `id` is not part of the ORTB native spec, is this intentional? - id: br.bidId, - ver: '1.2', - request: createNativeRequest(br) - }; - } - return impObject; - }); - - // TODO: do these values make sense? - let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - let r = bidderRequest.refererInfo.ref; - - let data = { - id: bidderRequest.bidderRequestId, - cur: [ DEFAULT_CUR ], - device: { - w: screen.width, - h: screen.height, - language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', - ua: navigator.userAgent, - }, - site: { - domain: parseUrl(page).hostname, - page: page, - }, - tmax: bidderRequest.timeout, - imp - }; - - if (r) { - data.site.ref = r; - } - - if (bidderRequest.gdprConsent) { - data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; - data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}}; - } - - if (bidderRequest.uspConsent !== undefined) { - if (!data['regs'])data['regs'] = {'ext': {}}; - data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent; - } - - if (config.getConfig('coppa') === true) { - if (!data['regs'])data['regs'] = {'coppa': 1}; - else data['regs']['coppa'] = 1; - } - - if (validBidRequests[0].schain) { - data['source'] = {'ext': {'schain': validBidRequests[0].schain}}; - } - - return { - method: 'POST', - url: endpointURL, - data: data - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - if (!serverResponse || isEmpty(serverResponse.body)) return []; - - let bids = []; - serverResponse.body.seatbid.forEach(response => { - response.bid.forEach(bid => { - let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner'; - - let bidObj = { - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - ttl: 1200, - currency: DEFAULT_CUR, - netRevenue: true, - creativeId: bid.crid, - dealId: bid.dealid || null, - mediaType: mediaType - }; - - switch (mediaType) { - case 'video': - bidObj.vastUrl = bid.adm; - break; - case 'native': - bidObj.native = parseNative(bid.adm); - break; - default: - bidObj.ad = bid.adm; - } + isBidRequestValid: (bid) => !!(bid.params.placementId && bid.params.placementId.toString().length === 32), - bids.push(bidObj); - }); - }); + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT_URL, DEFAULT_CUR), - return bids; - }, + interpretResponse: (serverResponse) => interpretResponse(serverResponse, DEFAULT_CUR, parseNative), onBidWon: (bid) => { if (isStr(bid.nurl) && bid.nurl !== '') { @@ -175,90 +30,4 @@ export const spec = { } }; -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 => { - // TODO: `id` is not part of imp.video in ORTB; is this intentional? - let videoObj = {id: br.bidId}; - 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/bridBidAdapter.js b/modules/bridBidAdapter.js index f3fe1541886..c822f4d5c80 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -1,27 +1,17 @@ -import {createTrackPixelHtml, _each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; +import {_each, deepAccess, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getRefererInfo} from '../src/refererDetection.js'; +import {getAd, getSiteObj, getSyncResponse} from '../libraries/targetVideoUtils/bidderUtils.js' +import {GVLID, SOURCE, TIME_TO_LIVE, VIDEO_ENDPOINT_URL, VIDEO_PARAMS} from '../libraries/targetVideoUtils/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest */ - -const SOURCE = 'pbjs'; -const BIDDER_CODE = 'brid'; -const ENDPOINT_URL = 'https://pbs.prebrid.tv/openrtb2/auction'; -const GVLID = 934; -const TIME_TO_LIVE = 300; -const VIDEO_PARAMS = [ - 'api', 'linearity', 'maxduration', 'mimes', 'minduration', 'plcmt', - 'playbackmethod', 'protocols', 'startdelay' -]; - export const spec = { - code: BIDDER_CODE, + code: 'brid', gvlid: GVLID, supportedMediaTypes: [VIDEO], @@ -117,7 +107,7 @@ export const spec = { requests.push({ method: 'POST', - url: ENDPOINT_URL, + url: VIDEO_ENDPOINT_URL, data: JSON.stringify(postBody), options: { withCredentials: true @@ -138,92 +128,59 @@ export const spec = { */ interpretResponse: function(serverResponse, bidRequest) { const response = serverResponse.body; - const bidResponses = []; - - _each(response.seatbid, (resp) => { - _each(resp.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; - - const {ad, adUrl, vastUrl, vastXml} = getAd(bid); - - const bidResponse = { - requestId, - params, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.adid, - currency: response.cur, - netRevenue: false, - ttl: TIME_TO_LIVE, - meta: { - advertiserDomains: bid.adomain || [] - } - }; + let highestBid = null; + + if (response && response.seatbid && response.seatbid.length && response.seatbid[0].bid && response.seatbid[0].bid.length) { + _each(response.seatbid, (resp) => { + _each(resp.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; + + const {ad, adUrl, vastUrl, vastXml} = getAd(bid); + + const bidResponse = { + requestId, + params, + cpm: bid.price, + width: bid.w, + height: bid.h, + creativeId: bid.crid || bid.adid, + currency: response.cur, + netRevenue: false, + ttl: TIME_TO_LIVE, + meta: { + advertiserDomains: bid.adomain || [] + } + }; - if (vastUrl || vastXml) { - bidResponse.mediaType = VIDEO; - if (vastUrl) bidResponse.vastUrl = vastUrl; - if (vastXml) bidResponse.vastXml = vastXml; - } else { - bidResponse.ad = ad; - bidResponse.adUrl = adUrl; - }; + if (vastUrl || vastXml) { + bidResponse.mediaType = VIDEO; + if (vastUrl) bidResponse.vastUrl = vastUrl; + if (vastXml) bidResponse.vastXml = vastXml; + } else { + bidResponse.ad = ad; + bidResponse.adUrl = adUrl; + }; - bidResponses.push(bidResponse); + if (!highestBid || highestBid.cpm < bidResponse.cpm) { + highestBid = bidResponse; + } + }); }); - }); + } - return bidResponses; + return highestBid ? [highestBid] : []; }, -} - -/** - * Helper function to get ad - * - * @param {object} bid The bid. - * @return {object} ad object. - */ -function getAd(bid) { - let ad, adUrl, vastXml, vastUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - case VIDEO: - if (bid.adm.substr(0, 4) === 'http') { - vastUrl = bid.adm; - } else { - vastXml = bid.adm; - }; - break; - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - }; + /** + * Determine the user sync type (either 'iframe' or 'image') based on syncOptions. + * Construct the sync URL by appending required query parameters such as gdpr, ccpa, and coppa consents. + * Return an array containing an object with the sync type and the constructed URL. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + return getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, 'brid'); } - return {ad, adUrl, vastXml, vastUrl}; -} - -/** - * Helper function to get site object - * - * @return {object} siteObj. - */ -function getSiteObj() { - const refInfo = (getRefererInfo && getRefererInfo()) || {}; - - return { - page: refInfo.page, - ref: refInfo.ref, - domain: refInfo.domain - }; } registerBidder(spec); diff --git a/modules/brightMountainMediaBidAdapter.js b/modules/brightMountainMediaBidAdapter.js index 98b97286631..5e5b062889d 100644 --- a/modules/brightMountainMediaBidAdapter.js +++ b/modules/brightMountainMediaBidAdapter.js @@ -1,4 +1,4 @@ -import { generateUUID, deepAccess, logWarn, deepSetValue } from '../src/utils.js'; +import { generateUUID, deepAccess, logWarn, deepSetValue, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -224,7 +224,7 @@ function getFloor(bid, size) { size: size, }); - if (typeof floorInfo === 'object' && floorInfo.currency === 'USD') { + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD') { return parseFloat(floorInfo.floor); } } diff --git a/modules/brightcomBidAdapter.js b/modules/brightcomBidAdapter.js deleted file mode 100644 index 1fa1dac4e95..00000000000 --- a/modules/brightcomBidAdapter.js +++ /dev/null @@ -1,303 +0,0 @@ -import { - _each, - isArray, - getWindowTop, - getUniqueIdentifierStr, - deepSetValue, - logError, - logWarn, - createTrackPixelHtml, - getWindowSelf, - isFn, - isPlainObject, - getBidIdParameter -} 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 = 'brightcom'; -const URL = 'https://brightcombid.marphezis.com/hb'; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER], - gvlid: 883, - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; - -function buildRequests(bidReqs, bidderRequest) { - try { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.page; - } - const brightcomImps = []; - const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); - _each(bidReqs, function (bid) { - let bidSizes = (bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes) || bid.sizes; - bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => isArray(size)); - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = _getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) - ? _getViewability(element, 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 = _getBidFloor(bid); - if (bidFloor) { - imp.bidfloor = bidFloor; - } - brightcomImps.push(imp); - }); - const brightcomBidReq = { - id: getUniqueIdentifierStr(), - imp: brightcomImps, - site: { - domain: bidderRequest?.refererInfo?.domain || '', - page: referrer, - publisher: { - id: publisherId - } - }, - device: { - devicetype: _getDeviceType(), - w: screen.width, - h: screen.height - }, - tmax: bidderRequest?.timeout - }; - - if (bidderRequest && bidderRequest.gdprConsent) { - deepSetValue(brightcomBidReq, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - deepSetValue(brightcomBidReq, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest && bidderRequest.uspConsent) { - deepSetValue(brightcomBidReq, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(brightcomBidReq, 'regs.coppa', 1); - } - - if (bidReqs[0] && bidReqs[0].schain) { - deepSetValue(brightcomBidReq, 'source.ext.schain', bidReqs[0].schain) - } - - if (bidReqs[0] && bidReqs[0].userIdAsEids) { - deepSetValue(brightcomBidReq, 'user.ext.eids', bidReqs[0].userIdAsEids || []) - } - - if (bidReqs[0] && bidReqs[0].userId) { - deepSetValue(brightcomBidReq, 'user.ext.ids', bidReqs[0].userId || []) - } - - return { - method: 'POST', - url: URL, - data: JSON.stringify(brightcomBidReq), - }; - } catch (e) { - 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') { - logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); - return []; - } - const {body: {id, seatbid}} = serverResponse; - try { - const brightcomBidResponses = []; - if (id && - seatbid && - seatbid.length > 0 && - seatbid[0].bid && - seatbid[0].bid.length > 0) { - seatbid[0].bid.map(brightcomBid => { - brightcomBidResponses.push({ - requestId: brightcomBid.impid, - cpm: parseFloat(brightcomBid.price), - width: parseInt(brightcomBid.w), - height: parseInt(brightcomBid.h), - creativeId: brightcomBid.crid || brightcomBid.id, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: _getAdMarkup(brightcomBid), - ttl: 60, - meta: { - advertiserDomains: brightcomBid && brightcomBid.adomain ? brightcomBid.adomain : [] - } - }); - }); - } - return brightcomBidResponses; - } catch (e) { - 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 += createTrackPixelHtml(bid.nurl); - } - return adm; -} - -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; -} - -function _getViewability(element, topWin, {w, h} = {}) { - return getWindowTop().document.visibilityState === 'visible' - ? _getPercentInView(element, topWin, {w, h}) - : 0; -} - -function _isIframe() { - try { - return getWindowSelf() !== 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; -} - -function _getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.bidFloor ? bid.params.bidFloor : null; - } - - let floor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -registerBidder(spec); diff --git a/modules/brightcomBidAdapter.md b/modules/brightcomBidAdapter.md deleted file mode 100644 index 9f9aa0e5dd7..00000000000 --- a/modules/brightcomBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -``` -Module Name: Brightcom Bid Adapter -Module Type: Bidder Adapter -Maintainer: vladislavy@brightcom.com -``` - -# Description - -Brightcom's adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bids: [{ - bidder: 'brightcom', - params: { - publisherId: 2141020, - bidFloor: 0.01 - } - }] - }, { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'brightcom', - params: { - publisherId: 2141020 - } - }] - } -] -``` diff --git a/modules/brightcomSSPBidAdapter.js b/modules/brightcomSSPBidAdapter.js deleted file mode 100644 index 4750881da40..00000000000 --- a/modules/brightcomSSPBidAdapter.js +++ /dev/null @@ -1,321 +0,0 @@ -import { - isArray, - getWindowTop, - getUniqueIdentifierStr, - deepSetValue, - logError, - logWarn, - createTrackPixelHtml, - getWindowSelf, - isFn, - isPlainObject, getBidIdParameter, -} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import {ajax} from '../src/ajax.js'; - -const BIDDER_CODE = 'bcmssp'; -const URL = 'https://rt.marphezis.com/hb'; -const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' - -export const spec = { - code: BIDDER_CODE, - gvlid: 883, - supportedMediaTypes: [BANNER], - isBidRequestValid, - buildRequests, - interpretResponse, - onBidderError, - onTimeout, - onBidWon, - getUserSyncs, -}; - -function buildRequests(bidReqs, bidderRequest) { - try { - const impressions = bidReqs.map(bid => { - let bidSizes = bid?.mediaTypes?.banner?.sizes || bid.sizes; - bidSizes = ((isArray(bidSizes) && isArray(bidSizes[0])) ? bidSizes : [bidSizes]); - bidSizes = bidSizes.filter(size => isArray(size)); - const processedSizes = bidSizes.map(size => ({w: parseInt(size[0], 10), h: parseInt(size[1], 10)})); - - const element = document.getElementById(bid.adUnitCode); - const minSize = _getMinSize(processedSizes); - const viewabilityAmount = _isViewabilityMeasurable(element) ? _getViewability(element, 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 = _getBidFloor(bid); - - if (bidFloor) { - imp.bidfloor = bidFloor; - } - - return imp; - }) - - const referrer = bidderRequest?.refererInfo?.page || ''; - const publisherId = getBidIdParameter('publisherId', bidReqs[0].params); - - const payload = { - id: getUniqueIdentifierStr(), - imp: impressions, - site: { - domain: bidderRequest?.refererInfo?.domain || '', - page: referrer, - publisher: { - id: publisherId - } - }, - device: { - devicetype: _getDeviceType(), - w: screen.width, - h: screen.height - }, - tmax: bidderRequest?.timeout - }; - - if (bidderRequest?.gdprConsent) { - deepSetValue(payload, 'regs.ext.gdpr', +bidderRequest.gdprConsent.gdprApplies); - deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - } - - if (bidderRequest?.uspConsent) { - deepSetValue(payload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (config.getConfig('coppa') === true) { - deepSetValue(payload, 'regs.coppa', 1); - } - - if (bidReqs?.[0]?.schain) { - deepSetValue(payload, 'source.ext.schain', bidReqs[0].schain) - } - - if (bidReqs?.[0]?.userIdAsEids) { - deepSetValue(payload, 'user.ext.eids', bidReqs[0].userIdAsEids || []) - } - - if (bidReqs?.[0].userId) { - deepSetValue(payload, 'user.ext.ids', bidReqs[0].userId || []) - } - - return { - method: 'POST', - url: URL, - data: JSON.stringify(payload), - }; - } catch (e) { - logError(e, {bidReqs, bidderRequest}); - } -} - -function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { - return false; - } - - return true; -} - -function interpretResponse(serverResponse) { - let response = []; - if (!serverResponse.body || typeof serverResponse.body != 'object') { - logWarn('Brightcom server returned empty/non-json response: ' + JSON.stringify(serverResponse.body)); - return response; - } - - const {body: {id, seatbid}} = serverResponse; - - try { - if (id && seatbid && seatbid.length > 0 && seatbid[0].bid && seatbid[0].bid.length > 0) { - response = seatbid[0].bid.map(bid => { - return { - requestId: bid.impid, - cpm: parseFloat(bid.price), - width: parseInt(bid.w), - height: parseInt(bid.h), - creativeId: bid.crid || bid.id, - currency: 'USD', - netRevenue: true, - mediaType: BANNER, - ad: _getAdMarkup(bid), - ttl: 60, - meta: { - advertiserDomains: bid?.adomain || [] - } - }; - }); - } - } catch (e) { - logError(e, {id, seatbid}); - } - - return response; -} - -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; -} - -function onTimeout(timeoutData) { - if (timeoutData === null) { - return; - } - - _trackEvent('timeout', timeoutData); -} - -function onBidderError(errorData) { - if (errorData === null || !errorData.bidderRequest) { - return; - } - - _trackEvent('error', errorData.bidderRequest) -} - -function onBidWon(bid) { - if (bid === null) { - return; - } - - _trackEvent('bidwon', bid) -} - -function _trackEvent(endpoint, data) { - ajax(`${TRACK_EVENT_URL}/${endpoint}`, null, JSON.stringify(data), { - method: 'POST', - withCredentials: false - }); -} - -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 += createTrackPixelHtml(bid.nurl); - } - return adm; -} - -function _isViewabilityMeasurable(element) { - return !_isIframe() && element !== null; -} - -function _getViewability(element, topWin, {w, h} = {}) { - return getWindowTop().document.visibilityState === 'visible' ? _getPercentInView(element, topWin, {w, h}) : 0; -} - -function _isIframe() { - try { - return getWindowSelf() !== 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; -} - -function _getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return bid.params.bidFloor ? bid.params.bidFloor : null; - } - - let floor = bid.getFloor({ - currency: 'USD', mediaType: '*', size: '*' - }); - if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { - return floor.floor; - } - return null; -} - -registerBidder(spec); diff --git a/modules/brightcomSSPBidAdapter.md b/modules/brightcomSSPBidAdapter.md deleted file mode 100644 index 8d0e4ec70dc..00000000000 --- a/modules/brightcomSSPBidAdapter.md +++ /dev/null @@ -1,46 +0,0 @@ -# Overview - -``` -Module Name: Brightcom SSP Bid Adapter -Module Type: Bidder Adapter -Maintainer: alexandruc@brightcom.com -``` - -# Description - -Brightcom's adapter integration to the Prebid library. - -# Test Parameters - -``` -var adUnits = [ - { - code: 'test-leaderboard', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - bids: [{ - bidder: 'bcmssp', - params: { - publisherId: 2141020, - bidFloor: 0.01 - } - }] - }, { - code: 'test-banner', - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, - bids: [{ - bidder: 'bcmssp', - params: { - publisherId: 2141020 - } - }] - } -] -``` diff --git a/modules/britepoolIdSystem.js b/modules/britepoolIdSystem.js deleted file mode 100644 index dcc365faaac..00000000000 --- a/modules/britepoolIdSystem.js +++ /dev/null @@ -1,155 +0,0 @@ -/** - * This module adds BritePoolId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/britepoolIdSystem - * @requires module:modules/userId - */ - -import { isEmpty, triggerPixel, logError } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js'; -const PIXEL = 'https://px.britepool.com/new?partner_id=t'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - * @typedef {import('../modules/userId/index.js').SubmoduleParams} SubmoduleParams - */ - -/** @type {Submodule} */ -export const britepoolIdSubmodule = { - /** - * Used to link submodule with config - * @type {string} - */ - name: 'britepoolId', - /** - * Decode the stored id value for passing to bid requests - * @function - * @param {string} value - * @returns {{britepoolid:string}} - */ - decode(value) { - return (value && typeof value['primaryBPID'] === 'string') ? { 'britepoolid': value['primaryBPID'] } : null; - }, - /** - * Performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [submoduleConfig] - * @param {ConsentData|undefined} consentData - * @returns {function} - */ - 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') { - getterResponse = getter(params); - // First let's rule out that the response is not a function - if (typeof getterResponse !== 'function') { - // Optimization to return value from getter - return { - id: britepoolIdSubmodule.normalizeValue(getterResponse) - }; - } - } - if (isEmpty(params)) { - triggerPixel(PIXEL); - } - // Return for async operation - return { - callback: function(callback) { - if (errors.length > 0) { - errors.forEach(error => logError(error)); - callback(); - return; - } - if (getterResponse) { - // Resolve the getter function response - try { - getterResponse(function(response) { - callback(britepoolIdSubmodule.normalizeValue(response)); - }); - } catch (error) { - if (error !== '') logError(error); - callback(); - } - } else { - ajax(url, { - success: response => { - const responseObj = britepoolIdSubmodule.normalizeValue(response); - callback(responseObj ? { primaryBPID: responseObj.primaryBPID } : null); - }, - error: error => { - if (error !== '') logError(error); - callback(); - } - }, JSON.stringify(params), { customHeaders: headers, contentType: 'application/json', method: 'POST', withCredentials: true }); - } - } - } - }, - /** - * Helper method to create params for our API call - * @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 = {}; - 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') { - errors.push(`userIdTargeting - britepoolId submodule requires getter to be a function`); - return { errors }; - } - } else { - if (params.api_key) { - // Add x-api-key into the header - headers['x-api-key'] = params.api_key; - } - } - 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; - delete params.getter; - return { - params, - headers, - url, - getter, - errors - }; - }, - /** - * Helper method to normalize a JSON value - */ - normalizeValue(value) { - let valueObj = null; - if (typeof value === 'object') { - valueObj = value; - } else if (typeof value === 'string') { - try { - valueObj = JSON.parse(value); - } catch (error) { - logError(error); - } - } - return valueObj; - }, - eids: { - 'britepoolid': { - source: 'britepool.com', - atype: 3 - }, - } -}; - -submodule('userId', britepoolIdSubmodule); diff --git a/modules/britepoolIdSystem.md b/modules/britepoolIdSystem.md deleted file mode 100644 index 72edbe2324b..00000000000 --- a/modules/britepoolIdSystem.md +++ /dev/null @@ -1,42 +0,0 @@ -## BritePool User ID Submodule - -BritePool User ID Module. For assistance setting up your module please contact us at [prebid@britepool.com](prebid@britepool.com). - -### Prebid Params - -Individual params may be set for the BritePool User ID Submodule. -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'britepoolId', - storage: { - name: 'britepoolid', - type: 'cookie', - expires: 30 - }, - params: { - url: 'https://sandbox-api.britepool.com/v1/britepool/id', // optional - api_key: '3fdbe297-3690-4f5c-9e11-ee9186a6d77c', // provided by britepool - hash: '31c5543c1734d25c7206f5fd591525d0295bec6fe84ff82f946a34fe970a1e66', // example hash identifier (sha256) - ssid: '221aa074-57fc-453b-81f0-6c74f628cd5c' // example identifier - } - }] - } -}); -``` -## Parameter Descriptions for the `usersync` Configuration Section -The below parameters apply only to the BritePool User ID Module integration. - -| Param under usersync.userIds[] | Scope | Type | Description | Example | -| --- | --- | --- | --- | --- | -| name | Required | String | ID value for the BritePool module - `"britepoolId"` | `"britepoolId"` | -| params | Required | Object | Details for BritePool initialization. | | -| params.api_key | Required | String |BritePool API Key provided by BritePool | "3fdbe297-3690-4f5c-9e11-ee9186a6d77c" | -| params.url | Optional | String |BritePool API url | "https://sandbox-api.britepool.com/v1/britepool/id" | -| params.identifier | Required | String | Where identifier in the params object is the key name. At least one identifier is required. Available Identifiers `aaid` `dtid` `idfa` `ilid` `luid` `mmid` `msid` `mwid` `rida` `ssid` `hash` | `params.ssid` `params.aaid` | -| 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. | `"britepoolid"` | -| 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 BritePool 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 | `{"primaryBPID": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | diff --git a/modules/browsiRtdProvider.js b/modules/browsiRtdProvider.js index 01a38c63b69..6ac19b39c79 100644 --- a/modules/browsiRtdProvider.js +++ b/modules/browsiRtdProvider.js @@ -13,6 +13,7 @@ * @property {string} pubKey * @property {string} url * @property {?string} keyName + * @property {?string} splitKey */ import {deepClone, deepSetValue, isFn, isGptPubadsDefined, isNumber, logError, logInfo, generateUUID} from '../src/utils.js'; @@ -25,6 +26,7 @@ import {getGlobal} from '../src/prebidGlobal.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; +import {setKeyValue as setGptKeyValue} from '../libraries/gptUtils/gptUtils.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -33,6 +35,7 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; const MODULE_NAME = 'browsi'; const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: MODULE_NAME}); +const RANDOM = Math.floor(Math.random() * 10) + 1; /** @type {ModuleParams} */ let _moduleParams = {}; @@ -50,7 +53,7 @@ let _ic = {}; * @param {Object} data */ export function addBrowsiTag(data) { - let script = loadExternalScript(data.u, 'browsi'); + let script = loadExternalScript(data.u, MODULE_TYPE_RTD, 'browsi'); script.async = true; script.setAttribute('data-sitekey', _moduleParams.siteKey); script.setAttribute('data-pubkey', _moduleParams.pubKey); @@ -58,12 +61,15 @@ export function addBrowsiTag(data) { script.setAttribute('id', 'browsi-tag'); script.setAttribute('src', data.u); script.prebidData = deepClone(typeof data === 'string' ? Object(data) : data) + script.brwRandom = RANDOM; if (_moduleParams.keyName) { script.prebidData.kn = _moduleParams.keyName; } return script; } +export const setKeyValue = (key) => setGptKeyValue(key, RANDOM.toString()); + export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -380,6 +386,7 @@ function init(moduleConfig) { _moduleParams = moduleConfig.params; if (_moduleParams && _moduleParams.siteKey && _moduleParams.pubKey && _moduleParams.url) { collectData(); + setKeyValue(_moduleParams.splitKey); } else { logError('missing params for Browsi provider'); } diff --git a/modules/c1xBidAdapter.js b/modules/c1xBidAdapter.js index 79ba8cf499d..d1b51dcb27d 100644 --- a/modules/c1xBidAdapter.js +++ b/modules/c1xBidAdapter.js @@ -185,7 +185,7 @@ function getBidFloor(bidRequest) { } let floor = - floorInfo.floor || + floorInfo?.floor || bidRequest.params.bidfloor || bidRequest.params.floorPriceMap || 0; diff --git a/modules/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index e73564dacdb..fa441a4f4fa 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -230,11 +230,6 @@ export const spec = { return false; } - if (bid.bidder !== BIDDER_CODE) { - logWarn(BIDDER_CODE + ': Must use "cadent_aperture_mx" as bidder code.'); - return false; - } - if (!bid.params.tagid || !isStr(bid.params.tagid)) { logWarn(BIDDER_CODE + ': Missing tagid param or tagid present and not type String.'); return false; @@ -282,12 +277,13 @@ export const spec = { }; // adding gpid support - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot'); - if (!gpid) { - gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); - } + let gpid = + deepAccess(bid, 'ortb2Imp.ext.gpid') || + deepAccess(bid, 'ortb2Imp.ext.data.adserver.adslot') || + deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { - data.ext = {gpid: gpid.toString()}; + data.ext = { gpid: gpid.toString() }; } let typeSpecifics = isVideo ? { video: cadentAdapter.buildVideo(bid) } : { banner: cadentAdapter.buildBanner(bid) }; let bidfloorObj = bidfloor > 0 ? { bidfloor, bidfloorcur: DEFAULT_CUR } : {}; diff --git a/modules/carbonAnalyticsAdapter.js b/modules/carbonAnalyticsAdapter.js index 8d412ea015c..d67f1d3498d 100644 --- a/modules/carbonAnalyticsAdapter.js +++ b/modules/carbonAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { cloneEventArguments, deepAccess, generateUUID, logError } from '../src/utils.js'; +import { deepAccess, generateUUID, logError } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { getStorageManager } from '../src/storageManager.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -6,6 +6,7 @@ import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; +import * as utils from '../src/utils.js'; const CARBON_GVL_ID = 493; const ANALYTICS_VERSION = 'v1.0'; @@ -31,7 +32,7 @@ let timeLastAuctionEvent = null; export let carbonAdapter = Object.assign(adapter({analyticsHost, ANALYTICS_TYPE}), { track({eventType, args}) { - args = cloneEventArguments(args || {}); + args = args ? utils.deepClone(args) : {}; switch (eventType) { case EVENTS.AUCTION_END: { diff --git a/modules/carodaBidAdapter.js b/modules/carodaBidAdapter.js index cb7b5fbe7c5..a2370a13942 100644 --- a/modules/carodaBidAdapter.js +++ b/modules/carodaBidAdapter.js @@ -8,7 +8,8 @@ import { deepSetValue, logError, mergeDeep, - parseSizesInput + sizeTupleToRtbSize, + sizesToSizeTuples } from '../src/utils.js'; import { config } from '../src/config.js'; @@ -183,8 +184,8 @@ function getImps (validBidRequests, common) { const floorInfo = bid.getFloor ? bid.getFloor({ currency: common.currency || 'EUR' }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; + const bidfloor = floorInfo?.floor; + const bidfloorcur = floorInfo?.currency; const { ctok, placementId } = bid.params; const imp = { bid_id: bid.bidId, @@ -195,13 +196,7 @@ function getImps (validBidRequests, common) { }; const bannerParams = deepAccess(bid, 'mediaTypes.banner'); if (bannerParams && bannerParams.sizes) { - const sizes = parseSizesInput(bannerParams.sizes); - const format = sizes.map(size => { - const [width, height] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); + const format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); imp.banner = { format }; diff --git a/modules/ccxBidAdapter.js b/modules/ccxBidAdapter.js index b1fcb29e3d0..0a305a651cb 100644 --- a/modules/ccxBidAdapter.js +++ b/modules/ccxBidAdapter.js @@ -65,7 +65,7 @@ function _validateSizes (sizeObj, type) { return true } -function _buildBid (bid) { +function _buildBid (bid, bidderRequest) { let placement = {} placement.id = bid.bidId placement.secure = 1 @@ -105,6 +105,10 @@ function _buildBid (bid) { placement.ext = {'pid': bid.params.placementId} + if (bidderRequest.paapi?.enabled) { + placement.ext.ae = bid?.ortb2Imp?.ext?.ae + } + return placement } @@ -197,7 +201,7 @@ export const spec = { } _each(validBidRequests, function (bid) { - requestBody.imp.push(_buildBid(bid)) + requestBody.imp.push(_buildBid(bid, bidderRequest)) }) // Return the server request return { diff --git a/modules/pirIdSystem.js b/modules/ceeIdSystem.js similarity index 67% rename from modules/pirIdSystem.js rename to modules/ceeIdSystem.js index 233176028d3..30240e3f75f 100644 --- a/modules/pirIdSystem.js +++ b/modules/ceeIdSystem.js @@ -1,7 +1,7 @@ /** - * This module adds pirId to the User ID module + * This module adds ceeId to the User ID module * The {@link module:modules/userId} module is required - * @module modules/pirId + * @module modules/ceeId * @requires module:modules/userId */ @@ -15,18 +15,17 @@ import {domainOverrideToRootDomain} from '../libraries/domainOverrideToRootDomai * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const MODULE_NAME = 'pirId'; -const ID_TOKEN = 'WPxid'; +const MODULE_NAME = 'ceeId'; export const storage = getStorageManager({ moduleName: MODULE_NAME, moduleType: MODULE_TYPE_UID }); /** * Reads the ID token from local storage or cookies. * @returns {string|undefined} The ID token, or undefined if not found. */ -export const readId = () => storage.getDataFromLocalStorage(ID_TOKEN) || storage.getCookie(ID_TOKEN); +export const readId = tokenName => storage.getDataFromLocalStorage(tokenName) || storage.getCookie(tokenName); /** @type {Submodule} */ -export const pirIdSubmodule = { +export const ceeIdSubmodule = { name: MODULE_NAME, gvlid: 676, @@ -37,7 +36,7 @@ export const pirIdSubmodule = { * @returns {(Object|undefined)} */ decode(value) { - return typeof value === 'string' ? { 'pirId': value } : undefined; + return typeof value === 'string' ? { 'ceeId': value } : undefined; }, /** @@ -45,18 +44,20 @@ export const pirIdSubmodule = { * @function * @returns {(IdResponse|undefined)} */ - getId() { - const pirIdToken = readId(); + getId(config) { + const { params = {} } = config; + const { tokenName, value } = params + const ceeIdToken = value || readId(tokenName); - return pirIdToken ? { id: pirIdToken } : undefined; + return ceeIdToken ? { id: ceeIdToken } : undefined; }, domainOverride: domainOverrideToRootDomain(storage, MODULE_NAME), eids: { - 'pirId': { - source: 'pir.wp.pl', + 'ceeId': { + source: 'ceeid.eu', atype: 1 }, }, }; -submodule('userId', pirIdSubmodule); +submodule('userId', ceeIdSubmodule); diff --git a/modules/ceeIdSystem.md b/modules/ceeIdSystem.md new file mode 100644 index 00000000000..fe7a543748d --- /dev/null +++ b/modules/ceeIdSystem.md @@ -0,0 +1,31 @@ +# Overview + +Module Name: ceeIdSystem +Module Type: UserID Module +Maintainer: pawel.grudzien@grupawp.pl + +# Description + +User identification system for WPM + +### Prebid Params example + +``` +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360 + }, + params: { + tokenName: 'name' // Your custom name of token to read + value: 'tokenValue' // Optional param if you want to pass token value directly through setConfig (this param shouldn't be set if token value will be taken from cookie or LS) + } + }] + } +}); +``` diff --git a/modules/cleanioRtdProvider.js b/modules/cleanioRtdProvider.js index 75898053fa9..016a8d7edf9 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -11,6 +11,7 @@ import { loadExternalScript } from '../src/adloader.js'; import { logError, generateUUID, insertElement } from '../src/utils.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -47,6 +48,7 @@ let preloadStatus = 0; * @param {string} scriptURL The script URL to preload */ function pageInitStepPreloadScript(scriptURL) { + // TODO: this bypasses adLoader const linkElement = document.createElement('link'); linkElement.rel = 'preload'; linkElement.as = 'script'; @@ -61,7 +63,7 @@ function pageInitStepPreloadScript(scriptURL) { * @param {string} scriptURL The script URL to add to the page for protection */ function pageInitStepProtectPage(scriptURL) { - loadExternalScript(scriptURL, 'clean.io'); + loadExternalScript(scriptURL, MODULE_TYPE_RTD, 'clean.io'); } /** diff --git a/modules/cleanmedianetBidAdapter.js b/modules/cleanmedianetBidAdapter.js index 601a237baa8..01178d63872 100644 --- a/modules/cleanmedianetBidAdapter.js +++ b/modules/cleanmedianetBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { 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, + plcmt: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, 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 diff --git a/modules/codefuelBidAdapter.js b/modules/codefuelBidAdapter.js index a4accee3ce0..9f85b2b82cb 100644 --- a/modules/codefuelBidAdapter.js +++ b/modules/codefuelBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, isArray} from '../src/utils.js'; +import {isArray, setOnAny} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -148,15 +148,6 @@ function getDeviceType() { return 2; // 'desktop' } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - function flatten(arr) { return [].concat(...arr); } diff --git a/modules/colombiaBidAdapter.js b/modules/colombiaBidAdapter.js new file mode 100644 index 00000000000..0d25ca6cb60 --- /dev/null +++ b/modules/colombiaBidAdapter.js @@ -0,0 +1,107 @@ +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) { + if (validBidRequests.length === 0) { + return []; + } + let payloadArr = [] + let ctr = 1; + validBidRequests = 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) ? bidderRequest.refererInfo.referer : ''; + let mediaTypes = {} + let payload = { + v: 'hb1', + p: placementId, + pos: '~' + ctr, + w: width, + h: height, + cb: cb, + r: referrer, + uid: bidId, + t: 'i', + d: HOST_NAME, + fpc: params.fpc, + _u: window.location.href, + mediaTypes: Object.assign({}, mediaTypes, bidRequest.mediaTypes) + }; + if (params.keywords) payload.keywords = params.keywords; + if (params.category) payload.cat = params.category; + if (params.pageType) payload.pgt = params.pageType; + if (params.incognito) payload.ic = params.incognito; + if (params.dsmi) payload.smi = params.dsmi; + if (params.optout) payload.out = params.optout; + if (bidRequest && bidRequest.hasOwnProperty('ortb2Imp') && bidRequest.ortb2Imp.hasOwnProperty('ext')) { + payload.ext = bidRequest.ortb2Imp.ext; + if (bidRequest.ortb2Imp.ext.hasOwnProperty('gpid')) payload.pubAdCode = bidRequest.ortb2Imp.ext.gpid.split('#')[0]; + } + payloadArr.push(payload); + ctr++; + }); + return [{ + method: 'POST', + url: ENDPOINT_URL, + data: payloadArr, + }] + }, + interpretResponse: function(serverResponse, bidRequest) { + const bidResponses = []; + const res = serverResponse.body || serverResponse; + if (!res || res.length === 0) { + return bidResponses; + } + try { + res.forEach(response => { + const crid = response.creativeId || 0; + const width = response.width || 0; + const height = response.height || 0; + let cpm = response.cpm || 0; + 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: response.requestId, + cpm: cpm.toFixed(2), + width: response.width, + height: response.height, + creativeId: crid, + dealId: dealId, + currency: currency, + netRevenue: netRevenue, + ttl: config.getConfig('_bidderTimeout') || 300, + referrer: bidRequest.data.r, + ad: response.ad + }; + bidResponses.push(bidResponse); + } + }); + } catch (error) { + utils.logError(error); + } + return bidResponses; + } +} +registerBidder(spec); diff --git a/modules/colombiaBidAdapter.md b/modules/colombiaBidAdapter.md new file mode 100644 index 00000000000..c6ef5e6b749 --- /dev/null +++ b/modules/colombiaBidAdapter.md @@ -0,0 +1,31 @@ +# Overview + +``` +Module Name: COLOMBIA Bidder Adapter +Module Type: Bidder Adapter +Maintainer: colombiaonline@timesinteret.in +``` + +# Description + +Connect to COLOMBIA for bids. + +COLOMBIA adapter requires setup and approval from the COLOMBIA team. Please reach out to your account team or colombiaonline@timesinteret.in for more information. + +# Test Parameters +``` + var adUnits = [{ + code: 'test-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250],[728,90],[320,50]] + } + }, + bids: [{ + bidder: 'colombia', + params: { + placementId: '540799' + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/colossussspBidAdapter.js b/modules/colossussspBidAdapter.js index 5fe78ff932d..2abe9cb94a8 100644 --- a/modules/colossussspBidAdapter.js +++ b/modules/colossussspBidAdapter.js @@ -147,12 +147,11 @@ export const spec = { if (bid.schain) { placement.schain = bid.schain; } - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { placement.gpid = gpid; } if (bid.userId) { - getUserId(placement.eids, bid.userId.britepoolid, 'britepool.com'); getUserId(placement.eids, bid.userId.idl_env, 'identityLink'); getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); @@ -173,7 +172,7 @@ export const spec = { placement.mimes = mediaTypes[VIDEO].mimes; placement.protocols = mediaTypes[VIDEO].protocols; placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; + placement.placement = mediaTypes[VIDEO].plcmt; placement.skip = mediaTypes[VIDEO].skip; placement.skipafter = mediaTypes[VIDEO].skipafter; placement.minbitrate = mediaTypes[VIDEO].minbitrate; diff --git a/modules/compassBidAdapter.js b/modules/compassBidAdapter.js index addcdfebb27..f5d98312a63 100644 --- a/modules/compassBidAdapter.js +++ b/modules/compassBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'compass'; const AD_URL = 'https://sa-lb.deliverimp.com/pbjs'; const SYNC_URL = 'https://sa-cs.deliverimp.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/concertBidAdapter.js b/modules/concertBidAdapter.js index bd738a39bba..ec65ba11205 100644 --- a/modules/concertBidAdapter.js +++ b/modules/concertBidAdapter.js @@ -1,7 +1,7 @@ import { logWarn, logMessage, debugTurnedOn, generateUUID, deepAccess } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -128,7 +128,8 @@ export const spec = { meta: { advertiserDomains: bid && bid.adomain ? bid.adomain : [] }, creativeId: bid.creativeId, netRevenue: bid.netRevenue, - currency: bid.currency + currency: bid.currency, + ...(bid.dealid && { dealId: bid.dealid }), }; }); diff --git a/modules/confiantRtdProvider.js b/modules/confiantRtdProvider.js index 4c5475421bb..7aee63472c9 100644 --- a/modules/confiantRtdProvider.js +++ b/modules/confiantRtdProvider.js @@ -13,6 +13,7 @@ import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * Injects the Confiant Inc. configuration script into the page, based on proprtyId provided @@ -21,7 +22,8 @@ import { EVENTS } from '../src/constants.js'; function injectConfigScript(propertyId) { const scriptSrc = `https://cdn.confiant-integrations.net/${propertyId}/gpt_and_prebid/config.js`; - loadExternalScript(scriptSrc, 'confiant', () => {}); + loadExternalScript(scriptSrc, MODULE_TYPE_RTD, 'confiant', () => { + }); } /** diff --git a/modules/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 0b840db6c26..ea02b49d8ed 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -1,23 +1,46 @@ +import adapterManager from '../src/adapterManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { percentInView } from '../libraries/percentInView/percentInView.js'; + +import { config } from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { ajax } from '../src/ajax.js'; import { deepAccess, - isFn, - logError, + deepSetValue, + formatQS, + getWindowTop, isArray, - formatQS + isFn, + isNumber, + isStr, + logError } from '../src/utils.js'; import { + ADPOD, BANNER, + VIDEO, } from '../src/mediaTypes.js'; const BIDDER_CODE = 'connatix'; + const AD_URL = 'https://capi.connatix.com/rtb/hba'; const DEFAULT_MAX_TTL = '3600'; const DEFAULT_CURRENCY = 'USD'; +const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids'; +const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; // 30 days +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); +const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved'; +const IDENTITY_PROVIDER_RESOLVED_EVENT = 'cnx_identity_provider_resolved'; +let cnxIdsValues; + +const EVENTS_URL = 'https://capi.connatix.com/tr/am'; + +let context = {}; /* * Get the bid floor value from the bid object, either using the getFloor function or by accessing the 'params.bidfloor' property. @@ -34,17 +57,160 @@ export function getBidFloor(bid) { mediaType: '*', size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } catch (err) { logError(err); return 0; } } +export function validateBanner(mediaTypes) { + if (!mediaTypes[BANNER]) { + return true; + } + + const banner = deepAccess(mediaTypes, BANNER, {}); + return (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); +} + +export function validateVideo(mediaTypes) { + const video = mediaTypes[VIDEO]; + if (!video) { + return true; + } + + return video.context !== ADPOD; +} + +export function _getMinSize(sizes) { + if (!sizes || sizes.length === 0) return undefined; + return sizes.reduce((minSize, currentSize) => { + const minArea = minSize.w * minSize.h; + const currentArea = currentSize.w * currentSize.h; + return currentArea < minArea ? currentSize : minSize; + }); +} + +export function _canSelectViewabilityContainer() { + try { + window.top.document.querySelector('#viewability-container'); + return true; + } catch (e) { + return false; + } +} + +export function _isViewabilityMeasurable(element) { + if (!element) return false; + return _canSelectViewabilityContainer(element); +} + +export function _getViewability(element, topWin, { w, h } = {}) { + return topWin.document.visibilityState === 'visible' + ? percentInView(element, topWin, { w, h }) + : 0; +} + +export function detectViewability(bid) { + const { params, adUnitCode } = bid; + + const viewabilityContainerIdentifier = params.viewabilityContainerIdentifier; + + let element = null; + let bidParamSizes = null; + let minSize = []; + + if (isStr(viewabilityContainerIdentifier)) { + try { + element = document.querySelector(viewabilityContainerIdentifier) || window.top.document.querySelector(viewabilityContainerIdentifier); + if (element) { + bidParamSizes = [element.offsetWidth, element.offsetHeight]; + minSize = _getMinSize(bidParamSizes) + } + } catch (e) { + logError(`Error while trying to find viewability container element: ${viewabilityContainerIdentifier}`); + } + } + + if (!element) { + // Get the sizes from the mediaTypes object if it exists, otherwise use the sizes array from the bid object + bidParamSizes = bid.mediaTypes && bid.mediaTypes.banner && bid.mediaTypes.banner.sizes ? bid.mediaTypes.banner.sizes : bid.sizes; + bidParamSizes = typeof bidParamSizes === 'undefined' && bid.mediaType && bid.mediaType.video && bid.mediaType.video.playerSize ? bid.mediaType.video.playerSize : bidParamSizes; + bidParamSizes = typeof bidParamSizes === 'undefined' && bid.mediaType && bid.mediaType.video && isNumber(bid.mediaType.video.w) && isNumber(bid.mediaType.h) ? [bid.mediaType.video.w, bid.mediaType.video.h] : bidParamSizes; + minSize = _getMinSize(bidParamSizes ?? []) + element = document.getElementById(adUnitCode); + } + + if (_isViewabilityMeasurable(element)) { + const minSizeObj = { + w: minSize[0], + h: minSize[1] + } + return Math.round(_getViewability(element, getWindowTop(), minSizeObj)) + } + + return null; +} + +export function _getBidRequests(validBidRequests) { + return validBidRequests.map(bid => { + const { + bidId, + mediaTypes, + params, + sizes, + } = bid; + const { placementId, viewabilityContainerIdentifier } = params; + let detectedViewabilityPercentage = detectViewability(bid); + if (isNumber(detectedViewabilityPercentage)) { + detectedViewabilityPercentage = detectedViewabilityPercentage / 100; + } + return { + bidId, + mediaTypes, + sizes, + placementId, + floor: getBidFloor(bid), + hasViewabilityContainerId: Boolean(viewabilityContainerIdentifier), + declaredViewabilityPercentage: bid.params.viewabilityPercentage ?? null, + detectedViewabilityPercentage, + }; + }); +} + +/** + * Get ids from Prebid User ID Modules and add them to the payload + */ +function _handleEids(payload, validBidRequests) { + let bidUserIdAsEids = deepAccess(validBidRequests, '0.userIdAsEids'); + if (isArray(bidUserIdAsEids) && bidUserIdAsEids.length > 0) { + deepSetValue(payload, 'userIdList', bidUserIdAsEids); + } +} + +export function saveOnAllStorages(name, value, expirationTimeMs) { + const date = new Date(); + date.setTime(date.getTime() + expirationTimeMs); + const expires = `expires=${date.toUTCString()}`; + storage.setCookie(name, JSON.stringify(value), expires); + storage.setDataInLocalStorage(name, JSON.stringify(value)); + cnxIdsValues = value; +} + +export function readFromAllStorages(name) { + const fromCookie = storage.getCookie(name); + const fromLocalStorage = storage.getDataFromLocalStorage(name); + + const parsedCookie = fromCookie ? JSON.parse(fromCookie) : undefined; + const parsedLocalStorage = fromLocalStorage ? JSON.parse(fromLocalStorage) : undefined; + + return parsedCookie || parsedLocalStorage || undefined; +} + export const spec = { code: BIDDER_CODE, gvlid: 143, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /* * Validate the bid request. @@ -55,19 +221,24 @@ export const spec = { const bidId = deepAccess(bid, 'bidId'); const mediaTypes = deepAccess(bid, 'mediaTypes', {}); const params = deepAccess(bid, 'params', {}); - const bidder = deepAccess(bid, 'bidder'); - - const banner = deepAccess(mediaTypes, BANNER, {}); const hasBidId = Boolean(bidId); - const isValidBidder = (bidder === BIDDER_CODE); - const isValidSize = (Boolean(banner.sizes) && isArray(mediaTypes[BANNER].sizes) && mediaTypes[BANNER].sizes.length > 0); - const hasSizes = mediaTypes[BANNER] ? isValidSize : false; + const hasMediaTypes = Boolean(mediaTypes) && (Boolean(mediaTypes[BANNER]) || Boolean(mediaTypes[VIDEO])); + const isValidBanner = validateBanner(mediaTypes); + const isValidVideo = validateVideo(mediaTypes); + const isValidViewability = typeof params.viewabilityPercentage === 'undefined' || (isNumber(params.viewabilityPercentage) && params.viewabilityPercentage >= 0 && params.viewabilityPercentage <= 1); const hasRequiredBidParams = Boolean(params.placementId); - const isValid = isValidBidder && hasBidId && hasSizes && hasRequiredBidParams; + const isValid = hasBidId && hasMediaTypes && isValidBanner && isValidVideo && hasRequiredBidParams && isValidViewability; if (!isValid) { - logError(`Invalid bid request: isValidBidder: ${isValidBidder} hasBidId: ${hasBidId}, hasSizes: ${hasSizes}, hasRequiredBidParams: ${hasRequiredBidParams}`); + logError( + `Invalid bid request: + hasBidId: ${hasBidId}, + hasMediaTypes: ${hasMediaTypes}, + isValidBanner: ${isValidBanner}, + isValidVideo: ${isValidVideo}, + hasRequiredBidParams: ${hasRequiredBidParams}` + ); } return isValid; }, @@ -78,21 +249,13 @@ export const spec = { * Return an object containing the request method, url, and the constructed payload. */ buildRequests: (validBidRequests = [], bidderRequest = {}) => { - const bidRequests = validBidRequests.map(bid => { - const { - bidId, - mediaTypes, - params, - sizes, - } = bid; - return { - bidId, - mediaTypes, - sizes, - placementId: params.placementId, - floor: getBidFloor(bid), - }; - }); + const bidRequests = _getBidRequests(validBidRequests); + let userIds; + try { + userIds = readFromAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY) || cnxIdsValues; + } catch (error) { + userIds = cnxIdsValues; + } const requestPayload = { ortb2: bidderRequest.ortb2, @@ -100,13 +263,18 @@ export const spec = { uspConsent: bidderRequest.uspConsent, gppConsent: bidderRequest.gppConsent, refererInfo: bidderRequest.refererInfo, + identityProviderData: userIds, bidRequests, }; + _handleEids(requestPayload, validBidRequests); + + context = requestPayload; + return { method: 'POST', url: AD_URL, - data: requestPayload + data: context }; }, @@ -129,12 +297,13 @@ export const spec = { cpm: bidResponse.Cpm, ttl: bidResponse.Ttl || DEFAULT_MAX_TTL, currency: 'USD', - mediaType: BANNER, + mediaType: bidResponse.VastXml ? VIDEO : BANNER, netRevenue: true, width: bidResponse.Width, height: bidResponse.Height, creativeId: bidResponse.CreativeId, ad: bidResponse.Ad, + vastXml: bidResponse.VastXml, referrer: referrer, })); }, @@ -171,6 +340,24 @@ export const spec = { params['us_privacy'] = encodeURIComponent(uspConsent); } + window.addEventListener('message', function handler(event) { + if (!event.data || event.origin !== 'https://cds.connatix.com') { + return; + } + + if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) { + this.removeEventListener('message', handler); + event.stopImmediatePropagation(); + } + + if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) { + const response = event.data; + if (response.data) { + saveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY); + } + } + }, true) + const syncUrl = serverResponses[0].body.UserSyncEndpoint; const queryParams = Object.keys(params).length > 0 ? formatQS(params) : ''; @@ -179,7 +366,51 @@ export const spec = { type: 'iframe', url }]; - } + }, + + isConnatix: (aliasName) => { + if (!aliasName) { + return false; + } + + const originalBidderName = adapterManager.aliasRegistry[aliasName] || aliasName; + return originalBidderName === BIDDER_CODE; + }, + + /** + * Register bidder specific code, which will execute if the server response time is greater than auction timeout + */ + onTimeout: (timeoutData) => { + const connatixBidRequestTimeout = timeoutData.find(bidderRequest => spec.isConnatix(bidderRequest.bidder)); + + // Log only it is a timeout for Connatix + // Otherwise it is not relevant for us + if (!connatixBidRequestTimeout) { + return; + } + const requestTimeout = connatixBidRequestTimeout.timeout; + const timeout = isNumber(requestTimeout) ? requestTimeout : config.getConfig('bidderTimeout'); + spec.triggerEvent({type: 'Timeout', timeout, context}); + }, + + /** + * Register bidder specific code, which will execute if a bid from this bidder won the auction + */ + onBidWon(bidWinData) { + if (bidWinData == null) { + return; + } + const {bidder, cpm, requestId, bidId, adUnitCode, timeToRespond, auctionId} = bidWinData; + + spec.triggerEvent({type: 'BidWon', bestBidBidder: bidder, bestBidPrice: cpm, requestId, bidId, adUnitCode, timeToRespond, auctionId, context}); + }, + + triggerEvent(data) { + ajax(EVENTS_URL, null, JSON.stringify(data), { + method: 'POST', + withCredentials: false + }); + }, }; registerBidder(spec); diff --git a/modules/connectIdSystem.js b/modules/connectIdSystem.js index 2ebc68baa84..6926c69d453 100644 --- a/modules/connectIdSystem.js +++ b/modules/connectIdSystem.js @@ -313,12 +313,16 @@ export const connectIdSubmodule = { /** * Utility function that returns a boolean flag indicating if the user - * has opeted out via the Yahoo easy-opt-out mechanism. + * has opted out via the Yahoo easy-opt-out mechanism. * @returns {Boolean} */ userHasOptedOut() { try { - return localStorage.getItem(OVERRIDE_OPT_OUT_KEY) === '1'; + if (storage.localStorageIsEnabled()) { + return storage.getDataFromLocalStorage(OVERRIDE_OPT_OUT_KEY) === '1'; + } else { + return true; + } } catch { return false; } diff --git a/modules/connectadBidAdapter.js b/modules/connectadBidAdapter.js index b40ef30f6bc..982bff22585 100644 --- a/modules/connectadBidAdapter.js +++ b/modules/connectadBidAdapter.js @@ -1,9 +1,9 @@ -import { deepSetValue, logWarn } from '../src/utils.js'; +import { deepAccess, deepSetValue, mergeDeep, logWarn, generateUUID } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js' import {config} from '../src/config.js'; import {tryAppendQueryString} from '../libraries/urlUtils/urlUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; + const BIDDER_CODE = 'connectad'; const BIDDER_CODE_ALIAS = 'connectadrealtime'; const ENDPOINT_URL = 'https://i.connectad.io/api/v2'; @@ -31,25 +31,39 @@ export const spec = { return ret; } + const sellerDefinedAudience = deepAccess(bidderRequest, 'ortb2.user.data', config.getAnyConfig('ortb2.user.data')); + const sellerDefinedContext = deepAccess(bidderRequest, 'ortb2.site.content.data', config.getAnyConfig('ortb2.site.content.data')); + const data = Object.assign({ placements: [], time: Date.now(), - user: {}, - // TODO: does the fallback to window.location make sense? - url: bidderRequest.refererInfo?.page || window.location.href, + url: bidderRequest.refererInfo?.page, referrer: bidderRequest.refererInfo?.ref, - // TODO: please do not send internal data structures over the network - referrer_info: bidderRequest.refererInfo?.legacy, screensize: getScreenSize(), dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, language: navigator.language, ua: navigator.userAgent, - pversion: '$prebid.version$' + pversion: '$prebid.version$', + cur: 'USD', + user: {}, + regs: {}, + source: {}, + site: {}, + sda: sellerDefinedAudience, + sdc: sellerDefinedContext, + }); + + const ortb2Params = bidderRequest?.ortb2 || {}; + ['site', 'user', 'device', 'bcat', 'badv', 'regs'].forEach(entry => { + const ortb2Param = ortb2Params[entry]; + if (ortb2Param) { + mergeDeep(data, { [entry]: ortb2Param }); + } }); // coppa compliance if (config.getConfig('coppa') === true) { - deepSetValue(data, 'user.coppa', 1); + deepSetValue(data, 'regs.coppa', 1); } // adding schain object @@ -72,24 +86,49 @@ export const spec = { deepSetValue(data, 'user.ext.us_privacy', bidderRequest.uspConsent); } + // GPP Support + if (bidderRequest?.gppConsent?.gppString) { + deepSetValue(data, 'regs.gpp', bidderRequest.gppConsent.gppString); + deepSetValue(data, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); + } else if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(data, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(data, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } + + // DSA Support + if (bidderRequest?.ortb2?.regs?.ext?.dsa) { + deepSetValue(data, 'regs.ext.dsa', bidderRequest.ortb2.regs.ext.dsa); + } + // EIDS Support if (validBidRequests[0].userIdAsEids) { deepSetValue(data, 'user.ext.eids', validBidRequests[0].userIdAsEids); } + const tid = deepAccess(bidderRequest, 'ortb2.source.tid') + if (tid) { + deepSetValue(data, 'source.tid', tid) + } + data.tmax = bidderRequest.timeout; + validBidRequests.map(bid => { const placement = Object.assign({ - // TODO: fix transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - id: bid.transactionId, + id: generateUUID(), divName: bid.bidId, + tagId: bid.adUnitCode, pisze: bid.mediaTypes.banner.sizes[0] || bid.sizes[0], sizes: bid.mediaTypes.banner.sizes, - adTypes: getSize(bid.mediaTypes.banner.sizes || bid.sizes), bidfloor: getBidFloor(bid), siteId: bid.params.siteId, - networkId: bid.params.networkId + networkId: bid.params.networkId, + tid: bid.ortb2Imp?.ext?.tid }); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + if (gpid) { + placement.gpid = gpid; + } + if (placement.networkId && placement.siteId) { data.placements.push(placement); } @@ -127,12 +166,22 @@ 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.meta = { + advertiserDomains: decision && decision.adomain ? decision.adomain : [] + }; bid.ad = retrieveAd(decision); bid.currency = 'USD'; bid.creativeId = decision.adId; bid.ttl = 360; bid.netRevenue = true; + + if (decision.dsa) { + bid.meta = Object.assign({}, bid.meta, { dsa: decision.dsa }) + } + if (decision.category) { + bid.meta = Object.assign({}, bid.meta, { primaryCatId: decision.category }) + } + bidResponses.push(bid); } } @@ -141,15 +190,15 @@ export const spec = { return bidResponses; }, - transformBidParams: function (params, isOpenRtb) { - return convertTypes({ - 'siteId': 'number', - 'networkId': 'number' - }, params); - }, + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let syncEndpoint; - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { - let syncEndpoint = 'https://cdn.connectad.io/connectmyusers.php?'; + if (pixelType == 'iframe') { + syncEndpoint = 'https://sync.connectad.io/iFrameSyncer?'; + } else { + syncEndpoint = 'https://sync.connectad.io/ImageSyncer?'; + } if (gdprConsent) { syncEndpoint = tryAppendQueryString(syncEndpoint, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); @@ -163,73 +212,26 @@ export const spec = { syncEndpoint = tryAppendQueryString(syncEndpoint, 'us_privacy', uspConsent); } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gpp', gppConsent.gppString); + syncEndpoint = tryAppendQueryString(syncEndpoint, 'gpp_sid', gppConsent?.applicableSections?.join(',')); + } + if (config.getConfig('coppa') === true) { syncEndpoint = tryAppendQueryString(syncEndpoint, 'coppa', 1); } - if (syncOptions.iframeEnabled) { + if (syncOptions.iframeEnabled || syncOptions.pixelEnabled) { return [{ - type: 'iframe', + type: pixelType, url: syncEndpoint }]; } else { - logWarn('Bidder ConnectAd: Please activate iFrame Sync'); + logWarn('Bidder ConnectAd: No User-Matching allowed'); } } }; -const sizeMap = [ - null, - '120x90', - '200x200', - '468x60', - '728x90', - '300x250', - '160x600', - '120x600', - '300x100', - '180x150', - '336x280', - '240x400', - '234x60', - '88x31', - '120x60', - '120x240', - '125x125', - '220x250', - '250x250', - '250x90', - '0x0', - '200x90', - '300x50', - '320x50', - '320x480', - '185x185', - '620x45', - '300x125', - '800x250', - '980x120', - '980x150', - '320x150', - '300x300', - '200x600', - '320x500', - '320x320' -]; - -sizeMap[77] = '970x90'; -sizeMap[123] = '970x250'; -sizeMap[43] = '300x600'; -sizeMap[286] = '970x66'; -sizeMap[3230] = '970x280'; -sizeMap[429] = '486x60'; -sizeMap[374] = '700x500'; -sizeMap[934] = '300x1050'; -sizeMap[1578] = '320x100'; -sizeMap[331] = '320x250'; -sizeMap[3301] = '320x267'; -sizeMap[2730] = '728x250'; - function getBidFloor(bidRequest) { let floorInfo = {}; @@ -241,22 +243,11 @@ function getBidFloor(bidRequest) { }); } - let floor = floorInfo.floor || bidRequest.params.bidfloor || bidRequest.params.floorprice || 0; + let floor = floorInfo?.floor || bidRequest.params.bidfloor || bidRequest.params.floorprice || 0; return floor; } -function getSize(sizes) { - const result = []; - sizes.forEach(function(size) { - const index = sizeMap.indexOf(size[0] + 'x' + size[1]); - if (index >= 0) { - result.push(index); - } - }); - return result; -} - function retrieveAd(decision) { return decision.contents && decision.contents[0] && decision.contents[0].body; } diff --git a/modules/consentManagementGpp.js b/modules/consentManagementGpp.js index a7bbca62205..2d6c6583257 100644 --- a/modules/consentManagementGpp.js +++ b/modules/consentManagementGpp.js @@ -7,12 +7,12 @@ import {deepSetValue, isEmpty, isNumber, isPlainObject, isStr, logError, logInfo, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import {gppDataHandler} from '../src/adapterManager.js'; -import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; -import {cmpClient, MODE_CALLBACK, MODE_MIXED, MODE_RETURN} from '../libraries/cmp/cmpClient.js'; -import {GreedyPromise} from '../src/utils/promise.js'; +import {cmpClient, MODE_CALLBACK} from '../libraries/cmp/cmpClient.js'; +import {GreedyPromise, defer} from '../src/utils/promise.js'; import {buildActivityParams} from '../src/activities/params.js'; +import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -38,9 +38,6 @@ function lookupStaticConsentData(callbacks) { return pipeCallbacks(() => processCmpData(staticConsentData), callbacks); } -const GPP_10 = '1.0'; -const GPP_11 = '1.1'; - class GPPError { constructor(message, arg) { this.message = message; @@ -49,104 +46,22 @@ class GPPError { } export class GPPClient { - static CLIENTS = {}; - - static register(apiVersion, defaultVersion = false) { - this.apiVersion = apiVersion; - this.CLIENTS[apiVersion] = this; - if (defaultVersion) { - this.CLIENTS.default = this; - } - } - + apiVersion = '1.1'; static INST; - /** - * Ping the CMP to set up an appropriate client for it, and initialize it. - * - * @param mkCmp - * @returns {Promise<[GPPClient,Promise<{}>]>} a promise to two objects: - * - a GPPClient that talks the best GPP dialect we know for the CMP's version; - * - a promise to GPP data. - */ - static init(mkCmp = cmpClient) { - let inst = this.INST; - if (!inst) { - let err; - const reset = () => err && (this.INST = null); - inst = this.INST = this.ping(mkCmp).catch(e => { - err = true; - reset(); - throw e; + static get(mkCmp = cmpClient) { + if (this.INST == null) { + const cmp = mkCmp({ + apiName: '__gpp', + apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use), + mode: MODE_CALLBACK }); - reset(); - } - return inst.then(([client, pingData]) => [ - client, - client.initialized ? client.refresh() : client.init(pingData) - ]); - } - - /** - * Ping the CMP to determine its version and set up a client appropriate for it. - * - * @param mkCmp - * @returns {Promise<[GPPClient, {}]>} a promise to two objects: - * - a GPPClient that talks the best GPP dialect we know for the CMP's version; - * - the result from pinging the CMP. - */ - static ping(mkCmp = cmpClient) { - const cmpOptions = { - apiName: '__gpp', - apiArgs: ['command', 'callback', 'parameter'], // do not pass version - not clear what it's for (or what we should use) - }; - - // in 1.0, 'ping' should return pingData but ignore callback; - // in 1.1 it should not return anything but run the callback - // the following looks for either - but once the version is known, produce a client that knows whether the - // rest of the interactions should pick return values or pass callbacks - - const probe = mkCmp({...cmpOptions, mode: MODE_RETURN}); - return new GreedyPromise((resolve, reject) => { - if (probe == null) { - reject(new GPPError('GPP CMP not found')); - return; + if (cmp == null) { + throw new GPPError('GPP CMP not found'); } - let done = false; // some CMPs do both return value and callbacks - avoid repeating log messages - const pong = (result, success) => { - if (done) return; - if (success != null && !success) { - reject(result); - return; - } - if (result == null) return; - done = true; - const cmpVersion = result?.gppVersion; - const Client = this.getClient(cmpVersion); - if (cmpVersion !== Client.apiVersion) { - logWarn(`Unrecognized GPP CMP version: ${cmpVersion}. Continuing using GPP API version ${Client}...`); - } else { - logInfo(`Using GPP version ${cmpVersion}`); - } - const mode = Client.apiVersion === GPP_10 ? MODE_MIXED : MODE_CALLBACK; - const client = new Client( - cmpVersion, - mkCmp({...cmpOptions, mode}) - ); - resolve([client, result]); - }; - - probe({ - command: 'ping', - callback: pong - }).then((res) => pong(res, true), reject); - }).finally(() => { - probe && probe.close(); - }); - } - - static getClient(cmpVersion) { - return this.CLIENTS.hasOwnProperty(cmpVersion) ? this.CLIENTS[cmpVersion] : this.CLIENTS.default; + this.INST = new this(cmp); + } + return this.INST; } #resolve; @@ -155,11 +70,9 @@ export class GPPClient { initialized = false; - constructor(cmpVersion, cmp) { - this.apiVersion = this.constructor.apiVersion; - this.cmpVersion = cmp; + constructor(cmp) { this.cmp = cmp; - [this.#resolve, this.#reject] = [0, 1].map(slot => (result) => { + [this.#resolve, this.#reject] = ['resolve', 'reject'].map(slot => (result) => { while (this.#pending.length) { this.#pending.pop()[slot](result); } @@ -176,6 +89,9 @@ export class GPPClient { init(pingData) { const ready = this.updateWhenReady(pingData); if (!this.initialized) { + if (pingData.gppVersion !== this.apiVersion) { + logWarn(`Unrecognized GPP CMP version: ${pingData.apiVersion}. Continuing using GPP API version ${this.apiVersion}...`); + } this.initialized = true; this.cmp({ command: 'addEventListener', @@ -184,9 +100,18 @@ export class GPPClient { this.#reject(new GPPError('Received error response from CMP', event)); } else if (event?.pingData?.cmpStatus === 'error') { this.#reject(new GPPError('CMP status is "error"; please check CMP setup', event)); - } else if (this.isCMPReady(event?.pingData || {}) && this.events.includes(event?.eventName)) { + } else if (this.isCMPReady(event?.pingData || {}) && ['sectionChange', 'signalStatus'].includes(event?.eventName)) { this.#resolve(this.updateConsent(event.pingData)); } + // NOTE: according to https://github.com/InteractiveAdvertisingBureau/Global-Privacy-Platform/blob/main/Core/CMP%20API%20Specification.md, + // > [signalStatus] Event is called whenever the display status of the CMP changes (e.g. the CMP shows the consent layer). + // + // however, from real world testing, at least some CMPs only trigger 'cmpDisplayStatus' + // other CMPs may do something else yet; here we just look for 'signalStatus: not ready' on any event + // to decide if consent data is likely to change + if (consentData != null && event?.pingData != null && !this.isCMPReady(event.pingData)) { + consentData = null; + } } }); } @@ -194,7 +119,7 @@ export class GPPClient { } refresh() { - return this.cmp({command: 'ping'}).then(this.updateWhenReady.bind(this)); + return this.cmp({command: 'ping'}).then(this.init.bind(this)); } /** @@ -204,15 +129,14 @@ export class GPPClient { * @returns {Promise<{}>} a promise to GPP consent data */ updateConsent(pingData) { - return this.getGPPData(pingData).then((data) => { - if (data == null || isEmpty(data)) { - throw new GPPError('Received empty response from CMP', data); + return new GreedyPromise(resolve => { + if (pingData == null || isEmpty(pingData)) { + throw new GPPError('Received empty response from CMP', pingData); } - return processCmpData(data); - }).then((data) => { - logInfo('Retrieved GPP consent from CMP:', data); - return data; - }); + const consentData = processCmpData(pingData); + logInfo('Retrieved GPP consent from CMP:', consentData); + resolve(consentData); + }) } /** @@ -221,9 +145,9 @@ export class GPPClient { * @returns {Promise<{}>} */ nextUpdate() { - return new GreedyPromise((resolve, reject) => { - this.#pending.push([resolve, reject]); - }); + const def = defer(); + this.#pending.push(def); + return def.promise; } /** @@ -236,79 +160,23 @@ export class GPPClient { updateWhenReady(pingData) { return this.isCMPReady(pingData) ? this.updateConsent(pingData) : this.nextUpdate(); } -} - -// eslint-disable-next-line no-unused-vars -class GPP10Client extends GPPClient { - static { - super.register(GPP_10); - } - - events = ['sectionChange', 'cmpStatus']; - - isCMPReady(pingData) { - return pingData.cmpStatus === 'loaded'; - } - - getGPPData(pingData) { - const parsedSections = GreedyPromise.all( - (pingData.supportedAPIs || pingData.apiSupport || []).map((api) => this.cmp({ - command: 'getSection', - parameter: api - }).catch(err => { - logWarn(`Could not retrieve GPP section '${api}'`, err); - }).then((section) => [api, section])) - ).then(sections => { - // parse single section object into [core, gpc] to uniformize with 1.1 parsedSections - return Object.fromEntries( - sections.filter(([_, val]) => val != null) - .map(([api, section]) => { - const subsections = [ - Object.fromEntries(Object.entries(section).filter(([k]) => k !== 'Gpc')) - ]; - if (section.Gpc != null) { - subsections.push({ - SubsectionType: 1, - Gpc: section.Gpc - }); - } - return [api, subsections]; - }) - ); - }); - return GreedyPromise.all([ - this.cmp({command: 'getGPPData'}), - parsedSections - ]).then(([gppData, parsedSections]) => Object.assign({}, gppData, {parsedSections})); - } -} - -// eslint-disable-next-line no-unused-vars -class GPP11Client extends GPPClient { - static { - super.register(GPP_11, true); - } - - events = ['sectionChange', 'signalStatus']; isCMPReady(pingData) { return pingData.signalStatus === 'ready'; } - - getGPPData(pingData) { - return GreedyPromise.resolve(pingData); - } } /** * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(): Object} [mkCmp=cmpClient] - A function to create the CMP client. Defaults to `cmpClient`. */ export function lookupIabConsent({onSuccess, onError}, mkCmp = cmpClient) { - pipeCallbacks(() => GPPClient.init(mkCmp).then(([client, gppDataPm]) => gppDataPm), {onSuccess, onError}); + pipeCallbacks(() => GPPClient.get(mkCmp).refresh(), {onSuccess, onError}); } // add new CMPs here, with their dedicated lookup function @@ -369,20 +237,6 @@ function loadConsentData(cb) { } } -/** - * Like `loadConsentData`, but cache and re-use previously loaded data. - * @param cb - */ -function loadIfMissing(cb) { - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } else { - loadConsentData(cb); - } -} - /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this @@ -391,29 +245,7 @@ function loadIfMissing(cb) { * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('gpp', function requestBidsHook(fn, reqBidsConfigObj) { - loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { - if (errMsg) { - let log = logWarn; - if (shouldCancelAuction) { - log = logError; - errMsg = `${errMsg} Canceling auction as per consentManagement config.`; - } - log(errMsg, ...extraArgs); - } - - if (shouldCancelAuction) { - fn.stopTiming(); - if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { - reqBidsConfigObj.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } else { - fn.call(this, reqBidsConfigObj); - } - }); -}); +export const requestBidsHook = consentManagementHook('gpp', () => consentData, loadConsentData); function processCmpData(consentData) { if ( @@ -425,16 +257,15 @@ function processCmpData(consentData) { } ['usnatv1', 'uscav1'].forEach(section => { if (consentData?.parsedSections?.[section]) { - logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData) + logWarn(`Received invalid section from cmp: '${section}'. Some functionality may not work as expected`, consentData); } - }) + }); return storeConsentData(consentData); } /** * Stores CMP data locally in module to make information available in adaptermanager.js for later in the auction * @param {{}} gppData the result of calling a CMP's `getGPPData` (or equivalent) - * @param {{}} sectionData map from GPP section name to the result of calling a CMP's `getSection` (or equivalent) */ export function storeConsentData(gppData = {}) { consentData = { diff --git a/modules/consentManagement.js b/modules/consentManagementTcf.js similarity index 87% rename from modules/consentManagement.js rename to modules/consentManagementTcf.js index 346b241fc1f..7de273c1380 100644 --- a/modules/consentManagement.js +++ b/modules/consentManagementTcf.js @@ -8,11 +8,11 @@ import {deepSetValue, isNumber, isPlainObject, isStr, logError, logInfo, logWarn import {config} from '../src/config.js'; import {gdprDataHandler} from '../src/adapterManager.js'; import {includes} from '../src/polyfill.js'; -import {timedAuctionHook} from '../src/utils/perfMetrics.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; import {enrichFPD} from '../src/fpd/enrichment.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {cmpClient} from '../libraries/cmp/cmpClient.js'; +import {consentManagementHook} from '../libraries/consentManagement/cmUtils.js'; const DEFAULT_CMP = 'iab'; const DEFAULT_CONSENT_TIMEOUT = 10000; @@ -22,6 +22,7 @@ export let userCMP; export let consentTimeout; export let gdprScope; export let staticConsentData; +let dsaPlatform = false; let actionTimeout; let consentData; @@ -35,7 +36,9 @@ const cmpCallMap = { /** * This function reads the consent string from the config to obtain the consent information of the user. - * @param {function({})} onSuccess acts as a success callback when the value is read from config; pass along consentObject from CMP + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when the value is read from config; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} [options.onError] - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). Optional. */ function lookupStaticConsentData({onSuccess, onError}) { processCmpData(staticConsentData, {onSuccess, onError}) @@ -45,8 +48,10 @@ function lookupStaticConsentData({onSuccess, onError}) { * This function handles interacting with an IAB compliant CMP to obtain the consent information of the user. * Given the async nature of the CMP's API, we pass in acting success/error callback functions to exit this function * based on the appropriate result. - * @param {function({})} onSuccess acts as a success callback when CMP returns a value; pass along consentObjectfrom CMP - * @param {function(string, ...{}?)} cmpError acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging) + * @param {Object} options - An object containing the callbacks. + * @param {function(Object): void} options.onSuccess - Acts as a success callback when CMP returns a value; pass along consentObject from CMP. + * @param {function(string, ...Object?): void} options.onError - Acts as an error callback while interacting with CMP; pass along an error message (string) and any extra error arguments (purely for logging). + * @param {function(Object): void} options.onEvent - Acts as an event callback for processing TCF data events from CMP. */ function lookupIabConsent({onSuccess, onError, onEvent}) { function cmpResponseCallback(tcfData, success) { @@ -152,20 +157,6 @@ function loadConsentData(cb) { } } -/** - * Like `loadConsentData`, but cache and re-use previously loaded data. - * @param cb - */ -function loadIfMissing(cb) { - if (consentData) { - logInfo('User consent information already known. Pulling internally stored information...'); - // eslint-disable-next-line standard/no-callback-literal - cb(false); - } else { - loadConsentData(cb); - } -} - /** * If consentManagement module is enabled (ie included in setConfig), this hook function will attempt to fetch the * user's encoded consent string from the supported CMP. Once obtained, the module will store this @@ -174,29 +165,7 @@ function loadIfMissing(cb) { * @param {object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('gdpr', function requestBidsHook(fn, reqBidsConfigObj) { - loadIfMissing(function (shouldCancelAuction, errMsg, ...extraArgs) { - if (errMsg) { - let log = logWarn; - if (shouldCancelAuction) { - log = logError; - errMsg = `${errMsg} Canceling auction as per consentManagement config.`; - } - log(errMsg, ...extraArgs); - } - - if (shouldCancelAuction) { - fn.stopTiming(); - if (typeof reqBidsConfigObj.bidsBackHandler === 'function') { - reqBidsConfigObj.bidsBackHandler(); - } else { - logError('Error executing bidsBackHandler'); - } - } else { - fn.call(this, reqBidsConfigObj); - } - }); -}); +export const requestBidsHook = consentManagementHook('gdpr', () => consentData, loadConsentData); /** * This function checks the consent data provided by CMP to ensure it's in an expected state. @@ -278,6 +247,7 @@ export function setConsentConfig(config) { // if true, then gdprApplies should be set to true gdprScope = config.defaultGdprScope === true; + dsaPlatform = !!config.dsaPlatform; logInfo('consentManagement module has been activated...'); @@ -311,6 +281,9 @@ export function enrichFPDHook(next, fpd) { } deepSetValue(ortb2, 'user.ext.consent', consent.consentString); } + if (dsaPlatform) { + deepSetValue(ortb2, 'regs.ext.dsa.dsarequired', 3); + } return ortb2; })); } diff --git a/modules/consentManagementUsp.js b/modules/consentManagementUsp.js index 78ec13cb891..29a67af0631 100644 --- a/modules/consentManagementUsp.js +++ b/modules/consentManagementUsp.js @@ -163,10 +163,12 @@ export const requestBidsHook = timedAuctionHook('usp', function requestBidsHook( /** * This function checks the consent data provided by USPAPI to ensure it's in an expected state. * If it's bad, we exit the module depending on config settings. - * If it's good, then we store the value and exits the module. - * @param {object} consentObject required; object returned by USPAPI that contains user's consent choices - * @param {function(string)} onSuccess callback accepting the resolved consent USP consent string - * @param {function(string, ...{}?)} onError callback accepting error message and any extra error arguments (used purely for logging) + * If it's good, then we store the value and exit the module. + * + * @param {Object} consentObject - The object returned by USPAPI that contains the user's consent choices. + * @param {Object} callbacks - An object containing the callback functions. + * @param {function(string): void} callbacks.onSuccess - Callback accepting the resolved USP consent string. + * @param {function(string, ...Object?): void} callbacks.onError - Callback accepting an error message and any extra error arguments (used purely for logging). */ function processUspData(consentObject, {onSuccess, onError}) { const valid = !!(consentObject && consentObject.usPrivacy); diff --git a/modules/consumableBidAdapter.js b/modules/consumableBidAdapter.js index 30b081e53d3..cb802508de9 100644 --- a/modules/consumableBidAdapter.js +++ b/modules/consumableBidAdapter.js @@ -61,7 +61,8 @@ export const spec = { source: [{ 'name': 'prebidjs', 'version': '$prebid.version$' - }] + }], + lang: bidderRequest.ortb2.device.language, }, validBidRequests[0].params); if (bidderRequest && bidderRequest.gdprConsent) { diff --git a/modules/contentexchangeBidAdapter.js b/modules/contentexchangeBidAdapter.js index a6aa9262061..96a55d657d2 100644 --- a/modules/contentexchangeBidAdapter.js +++ b/modules/contentexchangeBidAdapter.js @@ -1,216 +1,21 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'contentexchange'; const AD_URL = 'https://eu2.adnetwork.agency/pbjs'; const SYNC_URL = 'https://sync2.adnetwork.agency'; const GVLID = 864; -function isBidResponseValid (bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency || !bid.meta) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData (bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, adFormat } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - placementId, - bidId, - adFormat, - schain, - bidfloor - }; - - switch (adFormat) { - case BANNER: - placement.sizes = mediaTypes[BANNER].sizes; - break; - case VIDEO: - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - break; - case NATIVE: - placement.native = mediaTypes[NATIVE]; - break; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, 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 = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - // TODO: does the fallback to 'window.location' make sense? - 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: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/contxtfulBidAdapter.js b/modules/contxtfulBidAdapter.js new file mode 100644 index 00000000000..acd9811b871 --- /dev/null +++ b/modules/contxtfulBidAdapter.js @@ -0,0 +1,216 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { _each, buildUrl, isStr, isEmptyStr, logInfo, logError } from '../src/utils.js'; +import { sendBeacon, ajax } from '../src/ajax.js'; +import { config as pbjsConfig } from '../src/config.js'; +import { + isBidRequestValid, + interpretResponse, + getUserSyncs as getUserSyncsLib, +} from '../libraries/teqblazeUtils/bidderUtils.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; + +// Constants +const BIDDER_CODE = 'contxtful'; +const BIDDER_ENDPOINT = 'prebid.receptivity.io'; +const MONITORING_ENDPOINT = 'monitoring.receptivity.io'; +const DEFAULT_NET_REVENUE = true; +const DEFAULT_TTL = 300; +const PREBID_VERSION = '$prebid.version$'; + +// ORTB conversion +const converter = ortbConverter({ + context: { + netRevenue: DEFAULT_NET_REVENUE, + ttl: DEFAULT_TTL + }, + imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const reqData = buildRequest(imps, bidderRequest, context); + return reqData; + }, + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + } +}); + +// Get Bid Floor +const _getRequestBidFloor = (mediaTypes, paramsBidFloor, bid) => { + const bidMediaType = Object.keys(mediaTypes)[0] || 'banner'; + const bidFloor = { floor: 0, currency: 'USD' }; + + if (typeof bid.getFloor === 'function') { + const { currency, floor } = bid.getFloor({ + mediaType: bidMediaType, + size: '*' + }); + floor && (bidFloor.floor = floor); + currency && (bidFloor.currency = currency); + } else if (paramsBidFloor) { + bidFloor.floor = paramsBidFloor + } + + return bidFloor; +} + +// Get Parameters from the config. +const extractParameters = (config) => { + const version = config?.contxtful?.version; + if (!isStr(version) || isEmptyStr(version)) { + throw Error(`contxfulBidAdapter: contxtful.version should be a non-empty string`); + } + + const customer = config?.contxtful?.customer; + if (!isStr(customer) || isEmptyStr(customer)) { + throw Error(`contxfulBidAdapter: contxtful.customer should be a non-empty string`); + } + + return { version, customer }; +} + +// Construct the Payload towards the Bidding endpoint +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const ortb2 = converter.toORTB({bidderRequest: bidderRequest, bidRequests: validBidRequests}); + + const bidRequests = []; + _each(validBidRequests, bidRequest => { + const { + mediaTypes = {}, + params = {}, + } = bidRequest; + bidRequest.bidFloor = _getRequestBidFloor(mediaTypes, params.bidfloor, bidRequest); + bidRequests.push(bidRequest) + }); + const config = pbjsConfig.getConfig(); + config.pbjsVersion = PREBID_VERSION; + const {version, customer} = extractParameters(config) + const adapterUrl = buildUrl({ + protocol: 'https', + host: BIDDER_ENDPOINT, + pathname: `/${version}/prebid/${customer}/bid`, + }); + + // https://docs.prebid.org/dev-docs/bidder-adaptor.html + let req = { + url: adapterUrl, + method: 'POST', + data: { + ortb2, + bidRequests, + bidderRequest, + config, + }, + }; + + return req; +}; + +// Prepare a sync object compatible with getUserSyncs. +const constructUrl = (userSyncsDefault, userSyncServer) => { + const urlSyncServer = (userSyncServer?.url ?? '').split('?'); + const userSyncUrl = userSyncsDefault?.url || ''; + const baseSyncUrl = urlSyncServer[0] || ''; + + let url = `${baseSyncUrl}${userSyncUrl}`; + + if (urlSyncServer.length > 1) { + const urlParams = urlSyncServer[1]; + url += url.includes('?') ? `&${urlParams}` : `?${urlParams}`; + } + + return { + ...userSyncsDefault, + url, + }; +}; + +// Returns the list of user synchronization objects. +const getUserSyncs = (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + // Get User Sync Defaults from pbjs lib + const userSyncsDefaultLib = getUserSyncsLib('')(syncOptions, null, gdprConsent, uspConsent, gppConsent); + const userSyncsDefault = userSyncsDefaultLib?.find(item => item.url !== undefined); + + // Map Server Responses to User Syncs list + const serverSyncsData = serverResponses?.flatMap(response => response.body || []) + .map(data => data.syncs) + .find(syncs => Array.isArray(syncs) && syncs.length > 0) || []; + const userSyncs = serverSyncsData + .map(sync => constructUrl(userSyncsDefault, sync)) + .filter(Boolean); // Filter out nulls + return userSyncs; +}; + +// Retrieve the sampling rate for events +const getSamplingRate = (bidderConfig, eventType) => { + const entry = Object.entries(bidderConfig?.contxtful?.sampling ?? {}).find(([key, value]) => key.toLowerCase() === eventType.toLowerCase()); + return entry ? entry[1] : 0.001; +}; + +// Handles the logging of events +const logEvent = (eventType, data) => { + try { + // Log event + logInfo(BIDDER_CODE, `[${eventType}] ${JSON.stringify(data)}`); + + // Get Config + const bidderConfig = pbjsConfig.getConfig(); + const {version, customer} = extractParameters(bidderConfig); + + // Sampled monitoring + if (['onBidBillable', 'onAdRenderSucceeded'].includes(eventType)) { + const randomNumber = Math.random(); + const samplingRate = getSamplingRate(bidderConfig, eventType); + if (randomNumber >= samplingRate) { + return; // Don't sample + } + } else if (!['onTimeout', 'onBidderError', 'onBidWon'].includes(eventType)) { + // Unsupported event type. + return; + } + + const payload = { type: eventType, data }; + const eventUrl = buildUrl({ + protocol: 'https', + host: MONITORING_ENDPOINT, + pathname: `/${version}/prebid/${customer}/log/${eventType}`, + }); + + // Try sending a beacon + if (sendBeacon(eventUrl, JSON.stringify(payload))) { + logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Beacon and payload: ${JSON.stringify(data)}`); + } else { + // Fallback to using ajax + ajax(eventUrl, null, JSON.stringify(payload), { + method: 'POST', + contentType: 'application/json', + withCredentials: true, + }); + logInfo(BIDDER_CODE, `[${eventType}] Logging data sent using Ajax and payload: ${JSON.stringify(data)}`); + } + } catch (error) { + logError(BIDDER_CODE, `Failed to log event: ${eventType}`); + } +}; + +// Bidder public specification +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs, + onBidWon: function(bid) { logEvent('onBidWon', bid); }, + onBidBillable: function(bid) { logEvent('onBidBillable', bid); }, + onAdRenderSucceeded: function(bid) { logEvent('onAdRenderSucceeded', bid); }, + onSetTargeting: function(bid) { }, + onTimeout: function(timeoutData) { logEvent('onTimeout', timeoutData); }, + onBidderError: function({ error, bidderRequest }) { logEvent('onBidderError', { error, bidderRequest }); }, +}; + +registerBidder(spec); diff --git a/modules/contxtfulBidAdapter.md b/modules/contxtfulBidAdapter.md new file mode 100644 index 00000000000..87a78c38a85 --- /dev/null +++ b/modules/contxtfulBidAdapter.md @@ -0,0 +1,98 @@ +# Overview + +``` +Module Name: Contxtful Bidder Adapter +Module Type: Bidder Adapter +Maintainer: contact@contxtful.com +``` + +# Description + +The Contxtful Bidder Adapter supports all mediatypes and connects to demand sources for bids. + +# Configuration +## Global Configuration +Contxtful uses the global configuration to store params once instead of duplicating for each ad unit. +Also, enabling user syncing greatly increases match rates and monetization. +Be sure to call `pbjs.setConfig()` only once. + +```javascript +pbjs.setConfig({ + debug: false, + contxtful: { + customer: '', // Required + version: '', // Required + }, + userSync: { + filterSettings: { + iframe: { + bidders: ['contxtful'], + filter: 'include' + } + } + } + // [...] +}); +``` + +## Bidder Setting +Contxtful leverages local storage for user syncing. + +```javascript +pbjs.bidderSettings = { + contxtful: { + storageAllowed: true + } +} +``` + +# Example Ad-units configuration +```javascript +var adUnits = [ + { + code: 'adunit1', + mediaTypes: { + banner: { + sizes: [ [300, 250], [320, 50] ], + } + }, + bids: [{ + bidder: 'contxtful', + }] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [{ + bidder: 'contxtful', + }] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [{ + bidder: 'contxtful', + }] + } +]; +``` diff --git a/modules/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 6d4b2a2ce29..55623c00591 100644 --- a/modules/contxtfulRtdProvider.js +++ b/modules/contxtfulRtdProvider.js @@ -1,8 +1,8 @@ /** - * Contxtful Technologies Inc - * This RTD module provides receptivity feature that can be accessed using the - * getReceptivity() function. The value returned by this function enriches the ad-units - * that are passed within the `getTargetingData` functions and GAM. + * Contxtful Technologies Inc. + * This RTD module provides receptivity that can be accessed using the + * getTargetingData and getBidRequestData functions. The receptivity enriches ad units + * and bid requests. */ import { submodule } from '../src/hook.js'; @@ -10,18 +10,84 @@ import { logInfo, logError, isStr, + mergeDeep, isEmptyStr, + isEmpty, buildUrl, + isArray, } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; const MODULE_NAME = 'contxtful'; const MODULE = `${MODULE_NAME}RtdProvider`; -const CONTXTFUL_RECEPTIVITY_DOMAIN = 'api.receptivity.io'; +const CONTXTFUL_HOSTNAME_DEFAULT = 'api.receptivity.io'; +const CONTXTFUL_DEFER_DEFAULT = 0; -let initialReceptivity = null; -let contxtfulModule = null; +const storageManager = getStorageManager({ + moduleType: MODULE_TYPE_RTD, + moduleName: MODULE_NAME, +}); + +let rxApi = null; +let isFirstBidRequestCall = true; + +/** + * Return current receptivity value for the requester. + * @param { String } requester + * @return { { Object } } + */ +function getRxEngineReceptivity(requester) { + return rxApi?.receptivity(requester); +} + +function getItemFromSessionStorage(key) { + try { + return storageManager.getDataFromSessionStorage(key); + } catch (error) { + logError(MODULE, error); + } +} + +function loadSessionReceptivity(requester) { + let sessionStorageValue = getItemFromSessionStorage(requester); + if (!sessionStorageValue) { + return null; + } + + try { + // Check expiration of the cached value + let sessionStorageReceptivity = JSON.parse(sessionStorageValue); + let expiration = parseInt(sessionStorageReceptivity?.exp); + if (expiration < new Date().getTime()) { + return null; + } + + let rx = sessionStorageReceptivity?.rx; + return rx; + } catch { + return null; + } +} + +/** + * Prepare a receptivity batch + * @param {Array.} requesters + * @param {Function} method + * @returns A batch + */ +function prepareBatch(requesters, method) { + return requesters.reduce((acc, requester) => { + const receptivity = method(requester); + if (!isEmpty(receptivity)) { + return { ...acc, [requester]: receptivity }; + } else { + return acc; + } + }, {}); +} /** * Init function used to start sub module @@ -30,12 +96,13 @@ let contxtfulModule = null; */ function init(config) { logInfo(MODULE, 'init', config); - initialReceptivity = null; - contxtfulModule = null; + rxApi = null; try { - const {version, customer, hostname} = extractParameters(config); - initCustomer(version, customer, hostname); + initCustomer(config); + + observeLastCursorPosition(); + return true; } catch (error) { logError(MODULE, error); @@ -51,7 +118,7 @@ function init(config) { * @return { { version: String, customer: String, hostname: String } } * @throws params.{name} should be a non-empty string */ -function extractParameters(config) { +export function extractParameters(config) { const version = config?.params?.version; if (!isStr(version) || isEmptyStr(version)) { throw Error(`${MODULE}: params.version should be a non-empty string`); @@ -62,89 +129,248 @@ function extractParameters(config) { throw Error(`${MODULE}: params.customer should be a non-empty string`); } - const hostname = config?.params?.hostname || CONTXTFUL_RECEPTIVITY_DOMAIN; + const hostname = config?.params?.hostname || CONTXTFUL_HOSTNAME_DEFAULT; + const defer = config?.params?.defer || CONTXTFUL_DEFER_DEFAULT; - return {version, customer, hostname}; + return { version, customer, hostname, defer }; } /** * Initialize sub module for a customer. * This will load the external resources for the sub module. - * @param { String } version - * @param { String } customer - * @param { String } hostname + * @param { String } config */ -function initCustomer(version, customer, hostname) { +function initCustomer(config) { + const { version, customer, hostname, defer } = extractParameters(config); const CONNECTOR_URL = buildUrl({ protocol: 'https', host: hostname, - pathname: `/${version}/prebid/${customer}/connector/p.js`, + pathname: `/${version}/prebid/${customer}/connector/rxConnector.js`, }); - const externalScript = loadExternalScript(CONNECTOR_URL, MODULE_NAME); - addExternalScriptEventListener(externalScript); + addConnectorEventListener(customer, config); + + const loadScript = () => loadExternalScript(CONNECTOR_URL, MODULE_TYPE_RTD, MODULE_NAME); + // Optionally defer the loading of the script + if (Number.isFinite(defer) && defer > 0) { + setTimeout(loadScript, defer); + } else { + loadScript(); + } } /** * Add event listener to the script tag for the expected events from the external script. - * @param { HTMLScriptElement } script + * @param { String } tagId + * @param { String } prebidConfig */ -function addExternalScriptEventListener(script) { - if (!script) { - return; - } +function addConnectorEventListener(tagId, prebidConfig) { + window.addEventListener( + 'rxConnectorIsReady', + async ({ detail: { [tagId]: rxConnector } }) => { + if (!rxConnector) { + return; + } + // Fetch the customer configuration + const { rxApiBuilder, fetchConfig } = rxConnector; + let config = await fetchConfig(tagId); + if (!config) { + return; + } + config['prebid'] = prebidConfig || {}; + rxApi = await rxApiBuilder(config); - script.addEventListener('initialReceptivity', ({ detail }) => { - let receptivityState = detail?.ReceptivityState; - if (isStr(receptivityState) && !isEmptyStr(receptivityState)) { - initialReceptivity = receptivityState; + // Remove listener now that we can use rxApi. + removeListeners(); } - }); + ); +} - script.addEventListener('rxEngineIsReady', ({ detail: api }) => { - contxtfulModule = api; - }); +/** + * Set targeting data for ad server + * @param { [String] } adUnits + * @param {*} config + * @param {*} _userConsent + * @return {{ code: { ReceptivityState: String } }} + */ +function getTargetingData(adUnits, config, _userConsent) { + try { + if (String(config?.params?.adServerTargeting) === 'false') { + return {}; + } + logInfo(MODULE, 'getTargetingData'); + + const requester = config?.params?.customer; + const rx = + getRxEngineReceptivity(requester) || + loadSessionReceptivity(requester) || + {}; + + if (isEmpty(rx)) { + return {}; + } + + return adUnits.reduce((targets, code) => { + targets[code] = rx; + return targets; + }, {}); + } catch (error) { + logError(MODULE, error); + return {}; + } } /** - * Return current receptivity. - * @return { { ReceptivityState: String } } + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} onDone Called on completion + * @param {Object} config Configuration for Contxtful RTD module + * @param {Object} userConsent */ -function getReceptivity() { +function getBidRequestData(reqBidsConfigObj, onDone, config, userConsent) { + function onReturn() { + if (isFirstBidRequestCall) { + isFirstBidRequestCall = false; + } + onDone(); + } + + logInfo(MODULE, 'getBidRequestData'); + const bidders = config?.params?.bidders || []; + if (isEmpty(bidders) || !isArray(bidders)) { + onReturn(); + return; + } + + let fromApi = rxApi?.receptivityBatched?.(bidders) || {}; + let fromStorage = prepareBatch(bidders, (bidder) => loadSessionReceptivity(`${config?.params?.customer}_${bidder}`)); + + let sources = [fromStorage, fromApi]; + if (isFirstBidRequestCall) { + sources.reverse(); + } + + let rxBatch = Object.assign(...sources); + + let singlePointEvents; + if (isEmpty(rxBatch)) { + singlePointEvents = btoa(JSON.stringify({ ui: getUiEvents() })); + } + + bidders + .forEach(bidderCode => { + const ortb2 = { + user: { + data: [ + { + name: MODULE_NAME, + ext: { + rx: rxBatch[bidderCode], + events: singlePointEvents, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + mergeDeep(reqBidsConfigObj.ortb2Fragments?.bidder, { + [bidderCode]: ortb2, + }); + }); + + onReturn(); +} + +function getUiEvents() { return { - ReceptivityState: contxtfulModule?.GetReceptivity()?.ReceptivityState || initialReceptivity + position: lastCursorPosition, + screen: getScreen(), }; } -/** - * Set targeting data for ad server - * @param { [String] } adUnits - * @param {*} _config - * @param {*} _userConsent - * @return {{ code: { ReceptivityState: String } }} - */ -function getTargetingData(adUnits, _config, _userConsent) { - logInfo(MODULE, 'getTargetingData'); - if (!adUnits) { - return {}; +function getScreen() { + function getInnerSize() { + let w = window?.innerWidth; + let h = window?.innerHeight; + + if (w && h) { + return [w, h]; + } } - const receptivity = getReceptivity(); - if (!receptivity?.ReceptivityState) { - return {}; + function getDocumentSize() { + let body = window?.document?.body; + let w = body.clientWidth; + let h = body.clientHeight; + + if (w && h) { + return [w, h]; + } } - return adUnits.reduce((targets, code) => { - targets[code] = receptivity; - return targets; - }, {}); + // If we cannot access or cast the window dimensions, we get None. + // If we cannot collect the size from the window we try to use the root document dimensions + let [width, height] = getInnerSize() || getDocumentSize() || [0, 0]; + let topLeft = { x: window.scrollX, y: window.scrollY }; + + return { + topLeft, + width, + height, + timestampMs: performance.now(), + }; +} + +let lastCursorPosition; + +function observeLastCursorPosition() { + function pointerEventToPosition(event) { + lastCursorPosition = { + x: event.clientX, + y: event.clientY, + timestampMs: performance.now() + }; + } + + function touchEventToPosition(event) { + let touch = event.touches.item(0); + if (!touch) { + return; + } + + lastCursorPosition = { + x: touch.clientX, + y: touch.clientY, + timestampMs: performance.now() + }; + } + + addListener('pointermove', pointerEventToPosition); + addListener('touchmove', touchEventToPosition); +} + +let listeners = {}; +function addListener(name, listener) { + listeners[name] = listener; + + window.addEventListener(name, listener); +} + +function removeListeners() { + for (const name in listeners) { + window.removeEventListener(name, listeners[name]); + delete listeners[name]; + } } export const contxtfulSubmodule = { name: MODULE_NAME, init, - extractParameters, getTargetingData, + getBidRequestData, }; submodule('realTimeData', contxtfulSubmodule); diff --git a/modules/contxtfulRtdProvider.md b/modules/contxtfulRtdProvider.md index dfefca2067a..de2376e782d 100644 --- a/modules/contxtfulRtdProvider.md +++ b/modules/contxtfulRtdProvider.md @@ -2,25 +2,42 @@ **Module Name:** Contxtful RTD Provider **Module Type:** RTD Provider -**Maintainer:** [prebid@contxtful.com](mailto:prebid@contxtful.com) +**Maintainer:** [contact@contxtful.com](mailto:contact@contxtful.com) # Description The Contxtful RTD module offers a unique feature—Receptivity. Receptivity is an efficiency metric, enabling the qualification of any instant in a session in real time based on attention. The core idea is straightforward: the likelihood of an ad’s success increases when it grabs attention and is presented in the right context at the right time. -To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please contact [prebid@contxtful.com](mailto:prebid@contxtful.com). - -# Configuration +To utilize this module, you need to register for an account with [Contxtful](https://contxtful.com). For inquiries, please reach out to [contact@contxtful.com](mailto:contact@contxtful.com). ## Build Instructions To incorporate this module into your `prebid.js`, compile the module using the following command: ```sh -gulp build --modules=contxtfulRtdProvider, +gulp build --modules=rtdModule,contxtfulRtdProvider, +``` + +## Testing + +To run the test server locally: +```sh +gulp serve --modules=rtdModule,contxtfulRtdProvider, --fix --nolint --notest +chrome http://localhost:9999/integrationExamples/gpt/contxtfulRtdProvider_example.html +``` + +To run the unit tests: + +```bash +gulp test ``` -## Module Configuration +To run the unit tests for a particular file: +```bash +gulp test --file "test/spec/modules/contxtfulRtdProvider_spec.js" --nolint +``` + +## Configuration Configure the `contxtfulRtdProvider` by passing the required settings through the `setConfig` function in `prebid.js`. @@ -35,8 +52,11 @@ pbjs.setConfig({ "name": "contxtful", "waitForIt": true, "params": { - "version": "", - "customer": "" + "version": "Contact contact@contxtful.com for the API version", + "customer": "Contact contact@contxtful.com for the customer ID", + "hostname": "api.receptivity.io", // Optional, default: "api.receptivity.io" + "bidders": ["bidderCode1", "bidderCode", "..."], // list of bidders + "adServerTargeting": true, // Optional, default: true } } ] @@ -44,22 +64,42 @@ pbjs.setConfig({ }); ``` -### Configuration Parameters +## Parameters -| Name | Type | Scope | Description | -|------------|----------|----------|-------------------------------------------| -| `version` | `string` | Required | Specifies the API version of Contxtful. | -| `customer` | `string` | Required | Your unique customer identifier. | +| Name | Type | Scope | Description | +|---------------------|----------|----------|--------------------------------------------| +| `version` | `String` | Required | Specifies the version of the Contxtful Receptivity API. | +| `customer` | `String` | Required | Your unique customer identifier. | +| `hostname` | `String` | Optional | Target URL for CONTXTFUL external JavaScript file. Default is "api.receptivity.io". Changing default behaviour is not recommended. Please reach out to contact@contxtful.com if you experience issues. | +| `adServerTargeting` | `Boolean`| Optional | Enables the `getTargetingData` to inject targeting value in ad units. Setting to true enables the feature, false disables the feature. Default is true | +| `bidders` | `Array` | Optional | Setting this array enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed `bidders`. Default is `[]` (an empty array). RECOMMENDED : Add all the active bidders like this `["bidderCode1", "bidderCode", "..."]` | -# Usage +## Usage: Injection in Ad Servers The `contxtfulRtdProvider` module loads an external JavaScript file and authenticates with Contxtful APIs. The `getTargetingData` function then adds a `ReceptivityState` to each ad slot, which can have one of two values: `Receptive` or `NonReceptive`. ```json { "adUnitCode1": { "ReceptivityState": "Receptive" }, - "adUnitCode2": { "ReceptivityState": "NonReceptive" } + "adUnitCode2": { "ReceptivityState": "Receptive" } } ``` -This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process. \ No newline at end of file +This module also integrates seamlessly with Google Ad Manager, ensuring that the `ReceptivityState` is available as early as possible in the ad serving process. + +## Usage: Injection in ortb2 for bidders + +Setting the `bidders` field in the configuration parameters enables Receptivity in the `ortb2` object through `getBidRequestData` for all the listed bidders. +On a Bid Request Event, all bidders in the configuration will inherit the Receptivity data through `ortb2` +Default is `[]` (an empty array) + +RECOMMENDED : Add all the bidders active like this `["bidderCode1", "bidderCode", "..."]` + +## Links + +- [Basic Prebid.js Example](https://docs.prebid.org/dev-docs/examples/basic-example.html) +- [How Bid Adapters Should Read First Party Data](https://docs.prebid.org/features/firstPartyData.html#how-bid-adapters-should-read-first-party-data) +- [getBidRequestData](https://docs.prebid.org/dev-docs/add-rtd-submodule.html#getbidrequestdata) +- [getTargetingData](https://docs.prebid.org/dev-docs/add-rtd-submodule.html#gettargetingdata) +- [Contxtful Documentation](https://documentation.contxtful.com/) + diff --git a/modules/conversantAnalyticsAdapter.js b/modules/conversantAnalyticsAdapter.js index 44e712d54f7..520735d4e73 100644 --- a/modules/conversantAnalyticsAdapter.js +++ b/modules/conversantAnalyticsAdapter.js @@ -1,7 +1,6 @@ import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import adapterManager from '../src/adapterManager.js'; import {logInfo, logWarn, logError, logMessage, deepAccess, isInteger} from '../src/utils.js'; import {getRefererInfo} from '../src/refererDetection.js'; @@ -515,7 +514,7 @@ cnvrHelper.createPayload = function(payloadType, auctionId, timestamp) { cnvrSampleRate: initOptions.cnvr_sample_rate, auction: { auctionId: auctionId, - preBidVersion: getGlobal().version, + preBidVersion: '$prebid.version$', sid: initOptions.site_id, auctionTimestamp: timestamp }, diff --git a/modules/conversantBidAdapter.js b/modules/conversantBidAdapter.js index 76ff2a9e2ad..ea8488cf361 100644 --- a/modules/conversantBidAdapter.js +++ b/modules/conversantBidAdapter.js @@ -60,6 +60,7 @@ const converter = ortbConverter({ request: function (buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); request.at = 1; + request.cur = ['USD']; if (context.bidRequests) { const bidRequest = context.bidRequests[0]; setSiteId(bidRequest, request); @@ -95,7 +96,7 @@ const converter = ortbConverter({ }, response(buildResponse, bidResponses, ortbResponse, context) { const response = buildResponse(bidResponses, ortbResponse, context); - return response.bids; + return response; }, overrides: { imp: { @@ -176,7 +177,8 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: function(serverResponse, bidRequest) { - return converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); + const ortbBids = converter.fromORTB({request: bidRequest.data, response: serverResponse.body}); + return ortbBids; }, /** diff --git a/modules/copper6sspBidAdapter.js b/modules/copper6sspBidAdapter.js new file mode 100644 index 00000000000..e05ed241cc6 --- /dev/null +++ b/modules/copper6sspBidAdapter.js @@ -0,0 +1,21 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'copper6ssp'; +const AD_URL = 'https://endpoint.copper6.com/pbjs'; +const SYNC_URL = 'https://сsync.copper6.com'; +const GVLID = 1356; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/copper6sspBidAdapter.md b/modules/copper6sspBidAdapter.md new file mode 100755 index 00000000000..a414187022d --- /dev/null +++ b/modules/copper6sspBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Copper6SSP Bidder Adapter +Module Type: Copper6SSP Bidder Adapter +Maintainer: info@copper6.com +``` + +# Description + +Connects to Copper6SSP exchange for bids. +Copper6SSP 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: 'copper6ssp', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'copper6ssp', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'copper6ssp', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/cpmstarBidAdapter.js b/modules/cpmstarBidAdapter.js index e076fb4b0bb..772cb8c537c 100755 --- a/modules/cpmstarBidAdapter.js +++ b/modules/cpmstarBidAdapter.js @@ -1,8 +1,8 @@ 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 {getBidIdParameter} from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { VIDEO, BANNER } from '../src/mediaTypes.js'; +import { config } from '../src/config.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'cpmstar'; @@ -12,15 +12,18 @@ const ENDPOINT_PRODUCTION = 'https://server.cpmstar.com/view.aspx'; const DEFAULT_TTL = 300; const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_NET_REVENUE = true; -function fixedEncodeURIComponent(str) { - return encodeURIComponent(str).replace(/[!'()*]/g, function(c) { - return '%' + c.charCodeAt(0).toString(16); - }); -} +export const converter = ortbConverter({ + context: { + ttl: DEFAULT_TTL, + netRevenue: DEFAULT_NET_REVENUE + } +}); export const spec = { code: BIDDER_CODE, + gvlid: 1317, supportedMediaTypes: [BANNER, VIDEO], pageID: Math.floor(Math.random() * 10e6), @@ -43,22 +46,30 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { var requests = []; - // This reference to window.top can cause issues when loaded in an iframe if not protected with a try/catch. for (var i = 0; i < validBidRequests.length; i++) { var bidRequest = validBidRequests[i]; - var referer = bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : bidderRequest.refererInfo.domain; - referer = encodeURIComponent(referer); - var e = getBidIdParameter('endpoint', bidRequest.params); - var ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; - var mediaType = spec.getMediaType(bidRequest); - var playerSize = spec.getPlayerSize(bidRequest); - var videoArgs = '&fv=0' + (playerSize ? ('&w=' + playerSize[0] + '&h=' + playerSize[1]) : ''); - var url = ENDPOINT + '?media=' + mediaType + (mediaType == VIDEO ? videoArgs : '') + - '&json=c_b&mv=1&poolid=' + getBidIdParameter('placementId', bidRequest.params) + - '&reachedTop=' + encodeURIComponent(bidderRequest.refererInfo.reachedTop) + - '&requestid=' + bidRequest.bidId + - '&referer=' + encodeURIComponent(referer); + const referer = bidderRequest.refererInfo.page ? bidderRequest.refererInfo.page : bidderRequest.refererInfo.domain; + const e = utils.getBidIdParameter('endpoint', bidRequest.params); + const ENDPOINT = e == 'dev' ? ENDPOINT_DEV : e == 'staging' ? ENDPOINT_STAGING : ENDPOINT_PRODUCTION; + const url = new URL(ENDPOINT); + const body = {}; + const mediaType = spec.getMediaType(bidRequest); + const playerSize = spec.getPlayerSize(bidRequest); + url.searchParams.set('media', mediaType); + if (mediaType == VIDEO) { + url.searchParams.set('fv', 0); + if (playerSize) { + url.searchParams.set('w', playerSize?.[0]); + url.searchParams.set('h', playerSize?.[1]); + } + } + url.searchParams.set('json', 'c_b'); + url.searchParams.set('mv', 1); + url.searchParams.set('poolid', utils.getBidIdParameter('placementId', bidRequest.params)); + url.searchParams.set('reachedTop', bidderRequest.refererInfo.reachedTop); + url.searchParams.set('requestid', bidRequest.bidId); + url.searchParams.set('referer', referer); if (bidRequest.schain && bidRequest.schain.nodes) { var schain = bidRequest.schain; @@ -67,45 +78,49 @@ export const spec = { 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 || ''); + (node.asi || '') + ',' + + (node.sid || '') + ',' + + (node.hp || '') + ',' + + (node.rid || '') + ',' + + (node.name || '') + ',' + + (node.domain || ''); } - url += '&schain=' + schainString; + url.searchParams.set('schain', schainString); } if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString != null) { - url += '&gdpr_consent=' + bidderRequest.gdprConsent.consentString; + url.searchParams.set('gdpr_consent', bidderRequest.gdprConsent.consentString); } if (bidderRequest.gdprConsent.gdprApplies != null) { - url += '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? 1 : 0); + url.searchParams.set('gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); } } if (bidderRequest.uspConsent != null) { - url += '&us_privacy=' + bidderRequest.uspConsent; + url.searchParams.set('us_privacy', bidderRequest.uspConsent); } if (config.getConfig('coppa')) { - url += '&tfcd=' + (config.getConfig('coppa') ? 1 : 0); + url.searchParams.set('tfcd', (config.getConfig('coppa') ? 1 : 0)); } - let body = {}; let adUnitCode = bidRequest.adUnitCode; if (adUnitCode) { body.adUnitCode = adUnitCode; } if (mediaType == VIDEO) { body.video = utils.deepAccess(bidRequest, 'mediaTypes.video'); + } else if (mediaType == BANNER) { + body.banner = utils.deepAccess(bidRequest, 'mediaTypes.banner'); } + const ortb = converter.toORTB({ bidderRequest, bidRequests: [bidRequest] }); + Object.assign(body, ortb); + requests.push({ method: 'POST', - url: url, + url: url.toString(), bidRequest: bidRequest, data: body }); @@ -144,7 +159,7 @@ export const spec = { width: rawBid.width || 0, height: rawBid.height || 0, currency: rawBid.currency ? rawBid.currency : DEFAULT_CURRENCY, - netRevenue: rawBid.netRevenue ? rawBid.netRevenue : true, + netRevenue: rawBid.netRevenue ? rawBid.netRevenue : DEFAULT_NET_REVENUE, ttl: rawBid.ttl ? rawBid.ttl : DEFAULT_TTL, creativeId: rawBid.creativeid || 0, meta: { @@ -191,4 +206,5 @@ export const spec = { } }; + registerBidder(spec); diff --git a/modules/cpmstarBidAdapter.md b/modules/cpmstarBidAdapter.md index c227f19bfaf..66f13479c05 100755 --- a/modules/cpmstarBidAdapter.md +++ b/modules/cpmstarBidAdapter.md @@ -3,10 +3,7 @@ ``` Module Name: Cpmstar Bidder Adapter Module Type: Bidder Adapter -Maintainer: josh@cpmstar.com -gdpr_supported: true -usp_supported: true -coppa_supported: true +Maintainer: prebid@cpmstar.com ``` # Description diff --git a/modules/craftBidAdapter.js b/modules/craftBidAdapter.js index c751a18cc84..3161534a441 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -1,12 +1,13 @@ -import {getBidRequest, logError} from '../src/utils.js'; +import {getBidRequest} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; +import {interpretResponseUtil} from '../libraries/interpretResponseUtils/index.js'; const BIDDER_CODE = 'craft'; const URL_BASE = 'https://gacraft.jp/prebid-v3'; @@ -68,31 +69,14 @@ export const spec = { interpretResponse: function(serverResponse, {bidderRequest}) { try { - serverResponse = serverResponse.body; - const bids = []; - if (!serverResponse) { - return []; - } - if (serverResponse.error) { - let errorMessage = `in response for ${bidderRequest.bidderCode} adapter`; - if (serverResponse.error) { - errorMessage += `: ${serverResponse.error}`; + const bids = interpretResponseUtil(serverResponse, {bidderRequest}, serverBid => { + const rtbBid = getRtbBid(serverBid); + if (rtbBid && rtbBid.cpm !== 0 && includes(this.supportedMediaTypes, rtbBid.ad_type)) { + const bid = newBid(serverBid, rtbBid, bidderRequest); + bid.mediaType = parseMediaType(rtbBid); + return bid; } - 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; } catch (e) { return []; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index c95cbf7af73..d44f947dd3e 100644 --- a/modules/criteoBidAdapter.js +++ b/modules/criteoBidAdapter.js @@ -1,15 +1,14 @@ -import { deepAccess, generateUUID, isArray, logError, logInfo, logWarn, parseUrl } from '../src/utils.js'; -import { loadExternalScript } from '../src/adloader.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { config } from '../src/config.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { verify } from 'criteo-direct-rsa-validate/build/verify.js'; // ref#2 -import { getStorageManager } from '../src/storageManager.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; -import { Renderer } from '../src/Renderer.js'; -import { OUTSTREAM } from '../src/video.js'; -import { ajax } from '../src/ajax.js'; +import {deepAccess, deepSetValue, isArray, logError, logWarn, parseUrl, triggerPixel} from '../src/utils.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {getRefererInfo} from '../src/refererDetection.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; +import {Renderer} from '../src/Renderer.js'; +import {OUTSTREAM} from '../src/video.js'; +import {ajax} from '../src/ajax.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {ortb25Translator} from '../libraries/ortb2.5Translator/translator.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -20,35 +19,199 @@ import { ajax } from '../src/ajax.js'; */ const GVLID = 91; -export const ADAPTER_VERSION = 36; +export const ADAPTER_VERSION = 37; const BIDDER_CODE = 'criteo'; -const CDB_ENDPOINT = 'https://bidder.criteo.com/cdb'; +const CDB_ENDPOINT = 'https://grid-bidder.criteo.com/openrtb_2_5/pbjs/auction/request'; const PROFILE_ID_INLINE = 207; -export const PROFILE_ID_PUBLISHERTAG = 185; export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); const LOG_PREFIX = 'Criteo: '; +const TRANSLATOR = ortb25Translator(); -/* - 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 = 144; -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 PUBLISHER_TAG_OUTSTREAM_SRC = 'https://static.criteo.net/js/ld/publishertag.renderer.js' -const FAST_BID_PUBKEY_E = 65537; -const FAST_BID_PUBKEY_N = 'ztQYwCE5BU7T9CDM5he6rKoabstXRmkzx54zFPZkWbK530dwtLBDeaWBMxHBUT55CYyboR/EZ4efghPi3CoNGfGWezpjko9P6p2EwGArtHEeS4slhu/SpSIFMjG6fdrpRoNuIAMhq1Z+Pr/+HOd1pThFKeGFr2/NhtAg+TXAzaU='; - const OPTOUT_COOKIE_NAME = 'cto_optout'; const BUNDLE_COOKIE_NAME = 'cto_bundle'; const GUID_RETENTION_TIME_HOUR = 24 * 30 * 13; // 13 months const OPTOUT_RETENTION_TIME_HOUR = 5 * 12 * 30 * 24; // 5 years +/** + * Defines the generic oRTB converter and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 60 + }, + imp, + request, + bidResponse, + response +}); + +/** + * Builds an impression object for the ORTB 2.5 request. + * + * @param {function} buildImp - The function for building an imp object. + * @param {Object} bidRequest - The bid request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 imp object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + deepSetValue(imp, 'ext', { + ...bidRequest.params.ext, + ...imp.ext, + rwdd: imp.rwdd, + floors: getFloors(bidRequest), + bidder: { + publishersubid: params?.publisherSubId, + zoneid: params?.zoneId, + uid: params?.uid, + }, + }); + + delete imp.rwdd // oRTB 2.6 field moved to ext + + if (!context.fledgeEnabled && imp.ext.igs?.ae) { + delete imp.ext.igs.ae; + } + + if (hasVideoMediaType(bidRequest)) { + const paramsVideo = bidRequest.params.video; + if (paramsVideo !== undefined) { + deepSetValue(imp, 'video', { + ...imp.video, + skip: imp.video.skip || paramsVideo.skip || 0, + placement: imp.video.placement || paramsVideo.placement, + minduration: imp.video.minduration || paramsVideo.minduration, + playbackmethod: imp.video.playbackmethod || paramsVideo.playbackmethod, + startdelay: imp.video.startdelay || paramsVideo.startdelay || 0, + }) + } + deepSetValue(imp, 'video.ext', { + context: bidRequest.mediaTypes.video.context, + playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), + plcmt: bidRequest.mediaTypes.video.plcmt, + poddur: bidRequest.mediaTypes.video.adPodDurationSec, + rqddurs: bidRequest.mediaTypes.video.durationRangeSec, + }) + } + + if (imp.native && typeof imp.native.request !== 'undefined') { + let requestNative = JSON.parse(imp.native.request); + + // We remove the native asset requirements if we used the bypass to generate the imp + const hasAssetRequirements = requestNative.assets && + (requestNative.assets.length !== 1 || Object.keys(requestNative.assets[0]).length); + if (!hasAssetRequirements) { + delete requestNative.assets; + } + + deepSetValue(imp, 'native.request_native', requestNative); + delete imp.native.request; + } + + return imp; +} + +/** + * Builds a request object for the ORTB 2.5 request. + * + * @param {function} buildRequest - The function for building a request object. + * @param {Array} imps - An array of ORTB 2.5 impression objects. + * @param {Object} bidderRequest - The bidder request object. + * @param {Object} context - The context object. + * @returns {Object} The ORTB 2.5 request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + + // params.pubid should override publisher id + if (typeof context.publisherId !== 'undefined') { + if (typeof request.app !== 'undefined') { + deepSetValue(request, 'app.publisher.id', context.publisherId); + } else { + deepSetValue(request, 'site.publisher.id', context.publisherId); + } + } + + if (bidderRequest && bidderRequest.gdprConsent) { + deepSetValue(request, 'regs.ext.gdprversion', bidderRequest.gdprConsent.apiVersion); + } + + // Translate 2.6 OpenRTB request into 2.5 OpenRTB request + request = TRANSLATOR(request); + + return request; +} + +/** + * Build bid from oRTB 2.5 bid. + * + * @param buildBidResponse + * @param bid + * @param context + * @returns {*} + */ +function bidResponse(buildBidResponse, bid, context) { + context.mediaType = deepAccess(bid, 'ext.mediatype'); + if (context.mediaType === NATIVE && typeof bid.adm_native !== 'undefined') { + bid.adm = bid.adm_native; + delete bid.adm_native; + } + + let bidResponse = buildBidResponse(bid, context); + const {bidRequest} = context; + + bidResponse.currency = deepAccess(bid, 'ext.cur') + + if (typeof deepAccess(bid, 'ext.meta') !== 'undefined') { + deepSetValue(bidResponse, 'meta', { + ...bidResponse.meta, + ...bid.ext.meta + }); + } + if (typeof deepAccess(bid, 'ext.paf.content_id') !== 'undefined') { + deepSetValue(bidResponse, 'meta.paf.content_id', bid.ext.paf.content_id) + } + + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastUrl = bid.ext?.displayurl; + // if outstream video, add a default render for it. + if (deepAccess(bidRequest, 'mediaTypes.video.context') === OUTSTREAM) { + bidResponse.renderer = createOutstreamVideoRenderer(bid); + } + } + + return bidResponse; +} + +/** + * Builds bid response from the oRTB 2.5 bid response. + * + * @param buildResponse + * @param bidResponses + * @param ortbResponse + * @param context + * @returns * + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + let response = buildResponse(bidResponses, ortbResponse, context); + + const pafTransmission = deepAccess(ortbResponse, 'ext.paf.transmission'); + response.bids.forEach(bid => { + if (typeof pafTransmission !== 'undefined' && typeof deepAccess(bid, 'meta.paf.content_id') !== 'undefined') { + deepSetValue(bid, 'meta.paf.transmission', pafTransmission); + } else { + delete bid.meta.paf; + } + }); + + return response; +} + /** @type {BidderSpec} */ export const spec = { code: BIDDER_CODE, @@ -58,15 +221,10 @@ export const spec = { getUserSyncs: function (syncOptions, _, gdprConsent, uspConsent, gppConsent = {}) { let { gppString = '', applicableSections = [] } = gppConsent; - if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { - const fastBidVersion = config.getConfig('criteo.fastBidVersion'); - if (canFastBid(fastBidVersion)) { - return []; - } - - const refererInfo = getRefererInfo(); - const origin = 'criteoPrebidAdapter'; + const refererInfo = getRefererInfo(); + const origin = 'criteoPrebidAdapter'; + if (syncOptions.iframeEnabled && hasPurpose1Consent(gdprConsent)) { const queryParams = []; queryParams.push(`origin=${origin}`); queryParams.push(`topUrl=${refererInfo.domain}`); @@ -120,10 +278,14 @@ export const spec = { if (response.optout) { deleteFromAllStorages(BUNDLE_COOKIE_NAME); - saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR); + saveOnAllStorages(OPTOUT_COOKIE_NAME, true, OPTOUT_RETENTION_TIME_HOUR, refererInfo.domain); } else { if (response.bundle) { - saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); + saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR, refererInfo.domain); + } + + if (response.callbacks) { + response.callbacks.forEach(triggerPixel); } } }, true); @@ -191,50 +353,25 @@ export const spec = { * @return {ServerRequest} */ buildRequests: (bidRequests, bidderRequest) => { - let url; - let data; - let fpd = bidderRequest.ortb2 || {}; - - Object.assign(bidderRequest, { - publisherExt: fpd.site?.ext, - userExt: fpd.user?.ext, - ceh: config.getConfig('criteo.ceh'), - coppa: config.getConfig('coppa') - }); - - // If publisher tag not already loaded try to get it from fast bid - const fastBidVersion = config.getConfig('criteo.fastBidVersion'); - const canLoadPublisherTag = canFastBid(fastBidVersion); - if (!publisherTagAvailable() && canLoadPublisherTag) { - window.Criteo = window.Criteo || {}; - window.Criteo.usePrebidEvents = false; - - tryGetCriteoFastBid(); + bidRequests.forEach(bidRequest => { + if (hasNativeMediaType(bidRequest)) { + if (!checkNativeSendId(bidRequest)) { + logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); + } - const fastBidUrl = getFastBidUrl(fastBidVersion); - // Reload the PublisherTag after the timeout to ensure FastBid is up-to-date and tracking done properly - setTimeout(() => { - loadExternalScript(fastBidUrl, BIDDER_CODE); - }, bidderRequest.timeout); - } + // We support native request without assets requirements because we can fill them later on. + // This is a trick to fool oRTB converter isOpenRTBBidRequestValid(ortb) fn because it needs + // nativeOrtbRequest.assets to be non-empty. + if (deepAccess(bidRequest, 'nativeOrtbRequest.assets') == null) { + logWarn(LOG_PREFIX + 'native asset requirements are missing'); + deepSetValue(bidRequest, 'nativeOrtbRequest.assets', [{}]); + } + } + }); - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = new Criteo.PubTag.Adapters.Prebid( - PROFILE_ID_PUBLISHERTAG, - ADAPTER_VERSION, - bidRequests, - bidderRequest, - '$prebid.version$', - { createOutstreamVideoRenderer: createOutstreamVideoRenderer } - ); - url = adapter.buildCdbUrl(); - data = adapter.buildCdbRequest(); - } else { - const context = buildContext(bidRequests, bidderRequest); - url = buildCdbUrl(context); - data = buildCdbRequest(context, bidRequests, bidderRequest); - } + const context = buildContext(bidRequests, bidderRequest); + const url = buildCdbUrl(context); + const data = CONVERTER.toORTB({bidderRequest, bidRequests, context}); if (data) { return { method: 'POST', url, data, bidRequests }; @@ -247,131 +384,24 @@ export const spec = { * @return {Bid[] | {bids: Bid[], fledgeAuctionConfigs: object[]}} */ interpretResponse: (response, request) => { - const body = response.body || response; - - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(request); - if (adapter) { - return adapter.interpretResponse(body, request); - } + if (typeof response?.body == 'undefined') { + return []; // no bid } - const bids = []; - const fledgeAuctionConfigs = []; - - if (body && body.slots && isArray(body.slots)) { - body.slots.forEach(slot => { - const bidRequest = getAssociatedBidRequest(request.bidRequests, slot); - if (bidRequest) { - const bidId = bidRequest.bidId; - const bid = { - requestId: bidId, - cpm: slot.cpm, - currency: slot.currency, - netRevenue: true, - ttl: slot.ttl || 60, - creativeId: slot.creativecode, - width: slot.width, - height: slot.height, - dealId: slot.deal, - }; - if (body.ext?.paf?.transmission && slot.ext?.paf?.content_id) { - const pafResponseMeta = { - content_id: slot.ext.paf.content_id, - transmission: response.ext.paf.transmission - }; - bid.meta = Object.assign({}, bid.meta, { paf: pafResponseMeta }); - } - if (slot.adomain) { - bid.meta = Object.assign({}, bid.meta, { advertiserDomains: [slot.adomain].flat() }); - } - if (slot.ext?.meta?.networkName) { - bid.meta = Object.assign({}, bid.meta, { networkName: slot.ext.meta.networkName }) - } - if (slot.ext?.dsa) { - bid.meta = Object.assign({}, bid.meta, { dsa: slot.ext.dsa }) - } - if (slot.native) { - if (bidRequest.params.nativeCallback) { - bid.ad = createNativeAd(bidId, slot.native, bidRequest.params.nativeCallback); - } else { - bid.native = createPrebidNativeAd(slot.native); - bid.mediaType = NATIVE; - } - } else if (slot.video) { - bid.vastUrl = slot.displayurl; - bid.mediaType = VIDEO; - const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - // if outstream video, add a default render for it. - if (context === OUTSTREAM) { - bid.renderer = createOutstreamVideoRenderer(slot); - } - } else { - bid.ad = slot.creative; - } - bids.push(bid); - } - }); - } - - if (isArray(body.ext?.igi)) { - body.ext.igi.forEach((igi) => { - if (isArray(igi?.igs)) { - igi.igs.forEach((igs) => { - fledgeAuctionConfigs.push(igs); - }); - } - }); - } + const interpretedResponse = CONVERTER.fromORTB({response: response.body, request: request.data}); + const bids = interpretedResponse.bids || []; - if (fledgeAuctionConfigs.length) { + const fledgeAuctionConfigs = deepAccess(response.body, 'ext.igi')?.filter(igi => isArray(igi?.igs)) + .flatMap(igi => igi.igs); + if (fledgeAuctionConfigs?.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } return bids; }, - /** - * @param {TimedOutBid} timeoutData - */ - onTimeout: (timeoutData) => { - if (publisherTagAvailable() && Array.isArray(timeoutData)) { - var auctionsIds = []; - timeoutData.forEach((bid) => { - if (auctionsIds.indexOf(bid.auctionId) === -1) { - auctionsIds.push(bid.auctionId); - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleBidTimeout(); - } - }); - } - }, - - /** - * @param {Bid} bid - */ - onBidWon: (bid) => { - if (publisherTagAvailable() && bid) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleBidWon(bid); - } - }, - - /** - * @param {Bid} bid - */ - onSetTargeting: (bid) => { - if (publisherTagAvailable()) { - // eslint-disable-next-line no-undef - const adapter = Criteo.PubTag.Adapters.Prebid.GetAdapter(bid.auctionId); - adapter.handleSetTargeting(bid); - } - }, /** * @param {BidRequest[]} bidRequests @@ -398,12 +428,29 @@ function readFromAllStorages(name) { return fromCookie || fromLocalStorage || undefined; } -function saveOnAllStorages(name, value, expirationTimeHours) { +function saveOnAllStorages(name, value, expirationTimeHours, domain) { const date = new Date(); date.setTime(date.getTime() + (expirationTimeHours * 60 * 60 * 1000)); const expires = `expires=${date.toUTCString()}`; - storage.setCookie(name, value, expires); + const subDomains = domain.split('.'); + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1, subDomains.length).join('.'); + + try { + storage.setCookie(name, value, expires, null, '.' + domain); + + // Try to read the cookie to check if we wrote it + const check = storage.getCookie(name); + if (check && check === value) { + break; + } + } catch (error) { + + } + } + storage.setDataInLocalStorage(name, value); } @@ -412,43 +459,26 @@ function deleteFromAllStorages(name) { storage.removeDataFromLocalStorage(name); } -/** - * @return {boolean} - */ -function publisherTagAvailable() { - // eslint-disable-next-line no-undef - return typeof Criteo !== 'undefined' && Criteo.PubTag && Criteo.PubTag.Adapters && Criteo.PubTag.Adapters.Prebid; -} - /** * @param {BidRequest[]} bidRequests * @param bidderRequest */ function buildContext(bidRequests, bidderRequest) { - let referrer = ''; - if (bidderRequest && bidderRequest.refererInfo) { - referrer = bidderRequest.refererInfo.page; - } const queryString = parseUrl(bidderRequest?.refererInfo?.topmostLocation).search; - const context = { - url: referrer, + return { + url: bidderRequest?.refererInfo?.page || '', debug: queryString['pbt_debug'] === '1', noLog: queryString['pbt_nolog'] === '1', - amp: false, + fledgeEnabled: bidderRequest.paapi?.enabled, + amp: bidRequests.some(bidRequest => bidRequest.params.integrationMode === 'amp'), + networkId: bidRequests.find(bidRequest => bidRequest.params?.networkId)?.params.networkId, + publisherId: bidRequests.find(bidRequest => bidRequest.params?.pubid)?.params.pubid, }; - - bidRequests.forEach(bidRequest => { - if (bidRequest.params.integrationMode === 'amp') { - context.amp = true; - } - }); - - return context; } /** - * @param {CriteoContext} context + * @param {Object} context * @return {string} */ function buildCdbUrl(context) { @@ -484,6 +514,10 @@ function buildCdbUrl(context) { url += `&optout=1`; } + if (context.networkId) { + url += `&networkId=` + context.networkId; + } + return url; } @@ -499,185 +533,6 @@ function checkNativeSendId(bidRequest) { )); } -/** - * @param {CriteoContext} context - * @param {BidRequest[]} bidRequests - * @param bidderRequest - * @return {*} - */ -function buildCdbRequest(context, bidRequests, bidderRequest) { - let networkId; - let pubid; - let schain; - let userIdAsEids; - let regs = Object.assign({}, { - coppa: bidderRequest.coppa === true ? 1 : (bidderRequest.coppa === false ? 0 : undefined) - }, bidderRequest.ortb2?.regs); - const request = { - id: generateUUID(), - publisher: { - url: context.url, - ext: bidderRequest.publisherExt, - }, - regs: regs, - slots: bidRequests.map(bidRequest => { - if (!userIdAsEids) { - userIdAsEids = bidRequest.userIdAsEids; - } - networkId = bidRequest.params.networkId || networkId; - pubid = bidRequest.params.pubid || pubid; - schain = bidRequest.schain || schain; - const slot = { - slotid: bidRequest.bidId, - impid: bidRequest.adUnitCode, - transactionid: bidRequest.ortb2Imp?.ext?.tid - }; - if (bidRequest.params.zoneId) { - slot.zoneid = bidRequest.params.zoneId; - } - if (deepAccess(bidRequest, 'ortb2Imp.ext')) { - slot.ext = bidRequest.ortb2Imp.ext; - } - - if (deepAccess(bidRequest, 'ortb2Imp.rwdd')) { - slot.rwdd = bidRequest.ortb2Imp.rwdd; - } - - if (bidRequest.params.ext) { - slot.ext = Object.assign({}, slot.ext, bidRequest.params.ext); - } - if (bidRequest.nativeOrtbRequest?.assets) { - slot.ext = Object.assign({}, slot.ext, { assets: bidRequest.nativeOrtbRequest.assets }); - } - if (bidRequest.params.uid) { - slot.ext = Object.assign({}, slot.ext, { bidder: { uid: bidRequest.params.uid } }); - } - - if (bidRequest.params.publisherSubId) { - slot.publishersubid = bidRequest.params.publisherSubId; - } - - if (bidRequest.params.nativeCallback || hasNativeMediaType(bidRequest)) { - slot.native = true; - if (!checkNativeSendId(bidRequest)) { - logWarn(LOG_PREFIX + 'all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)'); - } - } - - if (hasBannerMediaType(bidRequest)) { - slot.sizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes'), parseSize); - } else { - slot.sizes = []; - } - - if (hasVideoMediaType(bidRequest)) { - const video = { - context: bidRequest.mediaTypes.video.context, - playersizes: parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize'), parseSize), - mimes: bidRequest.mediaTypes.video.mimes, - protocols: bidRequest.mediaTypes.video.protocols, - maxduration: bidRequest.mediaTypes.video.maxduration, - api: bidRequest.mediaTypes.video.api, - 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, - plcmt: bidRequest.mediaTypes.video.plcmt, - w: bidRequest.mediaTypes.video.w, - h: bidRequest.mediaTypes.video.h, - linearity: bidRequest.mediaTypes.video.linearity, - skipmin: bidRequest.mediaTypes.video.skipmin, - skipafter: bidRequest.mediaTypes.video.skipafter, - minbitrate: bidRequest.mediaTypes.video.minbitrate, - maxbitrate: bidRequest.mediaTypes.video.maxbitrate, - delivery: bidRequest.mediaTypes.video.delivery, - pos: bidRequest.mediaTypes.video.pos, - playbackend: bidRequest.mediaTypes.video.playbackend, - adPodDurationSec: bidRequest.mediaTypes.video.adPodDurationSec, - durationRangeSec: bidRequest.mediaTypes.video.durationRangeSec, - }; - 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; - } - - enrichSlotWithFloors(slot, bidRequest); - - if (!bidderRequest.fledgeEnabled && slot.ext?.ae) { - delete slot.ext.ae; - } - - return slot; - }), - }; - if (networkId) { - request.publisher.networkid = networkId; - } - - request.source = { - tid: bidderRequest.ortb2?.source?.tid - }; - - if (schain) { - request.source.ext = { - schain: schain - }; - }; - request.user = bidderRequest.ortb2?.user || {}; - request.site = bidderRequest.ortb2?.site || {}; - request.app = bidderRequest.ortb2?.app || {}; - - if (pubid) { - request.site.publisher = {...request.site.publisher, ...{ id: pubid }}; - request.app.publisher = {...request.app.publisher, ...{ id: pubid }}; - } - - request.device = bidderRequest.ortb2?.device || {}; - if (bidderRequest && bidderRequest.ceh) { - request.user.ceh = bidderRequest.ceh; - } - if (bidderRequest && bidderRequest.gdprConsent) { - request.gdprConsent = {}; - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - request.gdprConsent.gdprApplies = !!(bidderRequest.gdprConsent.gdprApplies); - } - request.gdprConsent.version = bidderRequest.gdprConsent.apiVersion; - if (typeof bidderRequest.gdprConsent.consentString !== 'undefined') { - request.gdprConsent.consentData = bidderRequest.gdprConsent.consentString; - } - } - if (bidderRequest && bidderRequest.uspConsent) { - request.user.uspIab = bidderRequest.uspConsent; - } - if (bidderRequest && bidderRequest.ortb2?.device?.sua) { - request.user.ext = request.user.ext || {}; - request.user.ext.sua = bidderRequest.ortb2?.device?.sua || {}; - } - if (userIdAsEids) { - request.user.ext = request.user.ext || {}; - request.user.ext.eids = [...userIdAsEids]; - } - if (bidderRequest && bidderRequest.ortb2?.bcat) { - request.bcat = bidderRequest.ortb2.bcat; - } - if (bidderRequest && bidderRequest.ortb2?.badv) { - request.badv = bidderRequest.ortb2.badv; - } - if (bidderRequest && bidderRequest.ortb2?.bapp) { - request.bapp = bidderRequest.ortb2.bapp; - } - request.tmax = bidderRequest.timeout; - return request; -} - function parseSizes(sizes, parser = s => s) { if (sizes == undefined) { return []; @@ -696,10 +551,6 @@ function hasVideoMediaType(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.video') !== undefined; } -function hasBannerMediaType(bidRequest) { - return deepAccess(bidRequest, 'mediaTypes.banner') !== undefined; -} - function hasNativeMediaType(bidRequest) { return deepAccess(bidRequest, 'mediaTypes.native') !== undefined; } @@ -710,63 +561,22 @@ function hasValidVideoMediaType(bidRequest) { var requiredMediaTypesParams = ['mimes', 'playerSize', 'maxduration', 'protocols', 'api', 'skip', 'placement', 'playbackmethod']; requiredMediaTypesParams.forEach(function (param) { - if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { - isValid = false; - logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); + if (param === 'placement') { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined && deepAccess(bidRequest, 'mediaTypes.video.plcmt') === undefined && deepAccess(bidRequest, 'params.video.plcmt') === undefined) { + isValid = false; + logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' or mediaTypes.video.plcmt is required'); + } + } else { + if (deepAccess(bidRequest, 'mediaTypes.video.' + param) === undefined && deepAccess(bidRequest, 'params.video.' + param) === undefined) { + isValid = false; + logError('Criteo Bid Adapter: mediaTypes.video.' + param + ' is required'); + } } }); return isValid; } -/** - * Create prebid compatible native ad with native payload - * @param {*} payload - * @returns prebid native ad assets - */ -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, - icon: payload.advertiser.logo, - image: payload.products[0].image, - clickUrl: payload.products[0].click_url, - privacyLink: payload.privacy.optout_click_url, - privacyIcon: payload.privacy.optout_image_url, - cta: payload.products[0].call_to_action, - price: payload.products[0].price, - impressionTrackers: payload.impression_pixels.map(pix => pix.url) - }; -} - -/** - * @param {string} id - * @param {*} payload - * @param {*} callback - * @return {string} - */ -function createNativeAd(id, payload, callback) { - // Store the callback and payload in a global object to be later accessed from the creative - var slotsName = 'criteo_prebid_native_slots'; - window[slotsName] = window[slotsName] || {}; - window[slotsName][id] = { callback, payload }; - - // The creative is in an iframe so we have to get the callback and payload - // from the parent window (doesn't work with safeframes) - return ` -`; -} - function pickAvailableGetFloorFunc(bidRequest) { if (bidRequest.getFloor) { return bidRequest.getFloor; @@ -785,87 +595,58 @@ function pickAvailableGetFloorFunc(bidRequest) { return undefined; } -function enrichSlotWithFloors(slot, bidRequest) { +function getFloors(bidRequest) { try { - const slotFloors = {}; + const floors = {}; const getFloor = pickAvailableGetFloorFunc(bidRequest); if (getFloor) { if (bidRequest.mediaTypes?.banner) { - slotFloors.banner = {}; + floors.banner = {}; const bannerSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes')) - bannerSizes.forEach(bannerSize => slotFloors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); + bannerSizes.forEach(bannerSize => floors.banner[parseSize(bannerSize).toString()] = getFloor.call(bidRequest, { size: bannerSize, mediaType: BANNER })); } if (bidRequest.mediaTypes?.video) { - slotFloors.video = {}; + floors.video = {}; const videoSizes = parseSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize')) - videoSizes.forEach(videoSize => slotFloors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); + videoSizes.forEach(videoSize => floors.video[parseSize(videoSize).toString()] = getFloor.call(bidRequest, { size: videoSize, mediaType: VIDEO })); } if (bidRequest.mediaTypes?.native) { - slotFloors.native = {}; - slotFloors.native['*'] = getFloor.call(bidRequest, { size: '*', mediaType: NATIVE }); + floors.native = {}; + floors.native['*'] = getFloor.call(bidRequest, { size: '*', mediaType: NATIVE }); } - if (Object.keys(slotFloors).length > 0) { - if (!slot.ext) { - slot.ext = {} - } - Object.assign(slot.ext, { - floors: slotFloors - }); - } + return floors; } } catch (e) { logError('Could not parse floors from Prebid: ' + e); } } -export function canFastBid(fastBidVersion) { - return fastBidVersion !== FAST_BID_VERSION_NONE; -} - -export function getFastBidUrl(fastBidVersion) { - let version; - if (fastBidVersion === FAST_BID_VERSION_LATEST) { - version = ''; - } else if (fastBidVersion) { - let majorVersion = String(fastBidVersion).split('.')[0]; - if (majorVersion < 102) { - logWarn('Specifying a Fastbid version which is not supporting version selection.') - } - version = '.' + fastBidVersion; - } else { - version = '.' + FAST_BID_VERSION_CURRENT; - } - - return PUBLISHER_TAG_URL_TEMPLATE.replace(FAST_BID_VERSION_PLACEHOLDER, version); -} - -function createOutstreamVideoRenderer(slot) { - if (slot.ext.videoPlayerConfig === undefined || slot.ext.videoPlayerType === undefined) { +function createOutstreamVideoRenderer(bid) { + if (bid.ext?.videoPlayerConfig === undefined || bid.ext?.videoPlayerType === undefined) { return undefined; } const config = { - documentResolver: (bid, sourceDocument, renderDocument) => { + documentResolver: (_, sourceDocument, renderDocument) => { return renderDocument ?? sourceDocument; } } - const render = (bid, renderDocument) => { + const render = (_, renderDocument) => { let payload = { - slotid: slot.impid, - vastUrl: slot.displayurl, - vastXml: slot.creative, + slotid: bid.id, + vastUrl: bid.ext?.displayurl, + vastXml: bid.adm, documentContext: renderDocument, }; - let outstreamConfig = slot.ext.videoPlayerConfig; - - window.CriteoOutStream[slot.ext.videoPlayerType].play(payload, outstreamConfig) + let outstreamConfig = bid.ext.videoPlayerConfig; + window.CriteoOutStream[bid.ext.videoPlayerType].play(payload, outstreamConfig) }; const renderer = Renderer.install({ url: PUBLISHER_TAG_OUTSTREAM_SRC, config: config }); @@ -873,60 +654,4 @@ function createOutstreamVideoRenderer(slot) { return renderer; } -function getAssociatedBidRequest(bidRequests, slot) { - for (const request of bidRequests) { - if (request.adUnitCode === slot.impid) { - if (request.params.zoneId && parseInt(request.params.zoneId) === slot.zoneid) { - return request; - } else if (slot.native) { - if (request.mediaTypes?.native || request.nativeParams) { - return request; - } - } else if (slot.video) { - if (request.mediaTypes?.video) { - return request; - } - } else if (request.mediaTypes?.banner || request.sizes) { - return request; - } - } - } - return undefined; -} - -export function tryGetCriteoFastBid() { - // begin ref#1 - try { - const fastBidStorageKey = 'criteo_fast_bid'; - const hashPrefix = '// Hash: '; - const fastBidFromStorage = storage.getDataFromLocalStorage(fastBidStorageKey); - - if (fastBidFromStorage !== null) { - // The value stored must contain the file's encrypted hash as first line - const firstLineEndPosition = fastBidFromStorage.indexOf('\n'); - const firstLine = fastBidFromStorage.substr(0, firstLineEndPosition).trim(); - - if (firstLine.substr(0, hashPrefix.length) !== hashPrefix) { - logWarn('No hash found in FastBid'); - storage.removeDataFromLocalStorage(fastBidStorageKey); - } else { - // Remove the hash part from the locally stored value - const publisherTagHash = firstLine.substr(hashPrefix.length); - const publisherTag = fastBidFromStorage.substr(firstLineEndPosition + 1); - - if (verify(publisherTag, publisherTagHash, FAST_BID_PUBKEY_N, FAST_BID_PUBKEY_E)) { - logInfo('Using Criteo FastBid'); - eval(publisherTag); // eslint-disable-line no-eval - } else { - logWarn('Invalid Criteo FastBid found'); - storage.removeDataFromLocalStorage(fastBidStorageKey); - } - } - } - } catch (e) { - // Unable to get fast bid - } - // end ref#1 -} - registerBidder(spec); diff --git a/modules/currency.js b/modules/currency.js index c26e64a9400..d040dc2cf49 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -6,11 +6,13 @@ import {config} from '../src/config.js'; import {getHook} from '../src/hook.js'; import {defer} from '../src/utils/promise.js'; import {registerOrtbProcessor, REQUEST} from '../src/pbjsORTB.js'; -import {timedBidResponseHook} from '../src/utils/perfMetrics.js'; +import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.js'; import {on as onEvent, off as offEvent} from '../src/events.js'; +import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js'; const DEFAULT_CURRENCY_RATE_URL = 'https://cdn.jsdelivr.net/gh/prebid/currency-file@1/latest.json?date=$$TODAY$$'; const CURRENCY_RATE_PRECISION = 4; +const MODULE_NAME = 'currency'; let ratesURL; let bidResponseQueue = []; @@ -26,8 +28,12 @@ let defaultRates; export let responseReady = defer(); +const delayedAuctions = timeoutQueue(); +let auctionDelay = 0; + /** * Configuration function for currency + * @param {object} config * @param {string} [config.adServerCurrency = 'USD'] * ISO 4217 3-letter currency code that represents the target currency. (e.g. 'EUR'). If this value is present, * the currency conversion feature is activated. @@ -61,13 +67,13 @@ export let responseReady = defer(); export function setConfig(config) { ratesURL = DEFAULT_CURRENCY_RATE_URL; - if (typeof config.rates === 'object') { + if (config.rates !== null && typeof config.rates === 'object') { currencyRates.conversions = config.rates; currencyRatesLoaded = true; needToCallForCurrencyFile = false; // don't call if rates are already specified } - if (typeof config.defaultRates === 'object') { + if (config.defaultRates !== null && typeof config.defaultRates === 'object') { defaultRates = config.defaultRates; // set up the default rates to be used if the rate file doesn't get loaded in time @@ -76,6 +82,7 @@ export function setConfig(config) { } if (typeof config.adServerCurrency === 'string') { + auctionDelay = config.auctionDelay; logInfo('enabling currency support', arguments); adServerCurrency = config.adServerCurrency; @@ -105,6 +112,7 @@ export function setConfig(config) { initCurrency(); } else { // currency support is disabled, setting defaults + auctionDelay = 0; logInfo('disabling currency support'); resetCurrency(); } @@ -136,6 +144,7 @@ function loadRates() { conversionCache = {}; currencyRatesLoaded = true; processBidResponseQueue(); + delayedAuctions.resume(); } catch (e) { errorSettingsRates('Failed to parse currencyRates response: ' + response); } @@ -144,6 +153,7 @@ function loadRates() { errorSettingsRates(...args); currencyRatesLoaded = true; processBidResponseQueue(); + delayedAuctions.resume(); needToCallForCurrencyFile = true; } } @@ -155,36 +165,37 @@ function loadRates() { function initCurrency() { conversionCache = {}; - currencySupportEnabled = true; - - logInfo('Installing addBidResponse decorator for currency module', arguments); - - // Adding conversion function to prebid global for external module and on page use - getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); - getHook('addBidResponse').before(addBidResponseHook, 100); - getHook('responsesReady').before(responsesReadyHook); - onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - onEvent(EVENTS.AUCTION_INIT, loadRates); - loadRates(); + if (!currencySupportEnabled) { + currencySupportEnabled = true; + // Adding conversion function to prebid global for external module and on page use + getGlobal().convertCurrency = (cpm, fromCurrency, toCurrency) => parseFloat(cpm) * getCurrencyConversion(fromCurrency, toCurrency); + getHook('addBidResponse').before(addBidResponseHook, 100); + getHook('responsesReady').before(responsesReadyHook); + getHook('requestBids').before(requestBidsHook, 50); + onEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + onEvent(EVENTS.AUCTION_INIT, loadRates); + loadRates(); + } } function resetCurrency() { - logInfo('Uninstalling addBidResponse decorator for currency module', arguments); - - getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); - getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); - offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); - offEvent(EVENTS.AUCTION_INIT, loadRates); - delete getGlobal().convertCurrency; - - adServerCurrency = 'USD'; - conversionCache = {}; - currencySupportEnabled = false; - currencyRatesLoaded = false; - needToCallForCurrencyFile = true; - currencyRates = {}; - bidderCurrencyDefault = {}; - responseReady = defer(); + if (currencySupportEnabled) { + getHook('addBidResponse').getHooks({hook: addBidResponseHook}).remove(); + getHook('responsesReady').getHooks({hook: responsesReadyHook}).remove(); + getHook('requestBids').getHooks({hook: requestBidsHook}).remove(); + offEvent(EVENTS.AUCTION_TIMEOUT, rejectOnAuctionTimeout); + offEvent(EVENTS.AUCTION_INIT, loadRates); + delete getGlobal().convertCurrency; + + adServerCurrency = 'USD'; + conversionCache = {}; + currencySupportEnabled = false; + currencyRatesLoaded = false; + needToCallForCurrencyFile = true; + currencyRates = {}; + bidderCurrencyDefault = {}; + responseReady = defer(); + } } function responsesReadyHook(next, ready) { @@ -335,3 +346,16 @@ export function setOrtbCurrency(ortbRequest, bidderRequest, context) { } registerOrtbProcessor({type: REQUEST, name: 'currency', fn: setOrtbCurrency}); + +export const requestBidsHook = timedAuctionHook('currency', function requestBidsHook(fn, reqBidsConfigObj) { + const continueAuction = ((that) => () => fn.call(that, reqBidsConfigObj))(this); + + if (!currencyRatesLoaded && auctionDelay > 0) { + delayedAuctions.submit(auctionDelay, continueAuction, () => { + logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction ${reqBidsConfigObj.auctionId}`) + continueAuction(); + }); + } else { + continueAuction(); + } +}); diff --git a/modules/cwireBidAdapter.js b/modules/cwireBidAdapter.js index f878be5f66a..16370c6e911 100644 --- a/modules/cwireBidAdapter.js +++ b/modules/cwireBidAdapter.js @@ -2,7 +2,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {BANNER} from '../src/mediaTypes.js'; import {generateUUID, getParameterByName, isNumber, logError, logInfo} from '../src/utils.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; +import { sendBeacon } from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -151,14 +152,18 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { - logError('placementId not provided or not a number'); - return false; - } + if (!bid.params?.domainId || !isNumber(bid.params.domainId)) { + logError('domainId not provided or not a number'); + if (!bid.params?.placementId || !isNumber(bid.params.placementId)) { + logError('placementId not provided or not a number'); + return false; + } - if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { - logError('pageId not provided or not a number'); - return false; + if (!bid.params?.pageId || !isNumber(bid.params.pageId)) { + logError('pageId not provided or not a number'); + return false; + } + return true; } return true; }, @@ -176,8 +181,8 @@ export const spec = { // process bid requests let processed = validBidRequests .map(bid => slotDimensions(bid)) - // Flattens the pageId and placement Id for backwards compatibility. - .map((bid) => ({...bid, pageId: bid.params?.pageId, placementId: bid.params?.placementId})); + // Flattens the pageId, domainId and placement Id for backwards compatibility. + .map((bid) => ({...bid, pageId: bid.params?.pageId, domainId: bid.params?.domainId, placementId: bid.params?.placementId})); const extensions = getCwExtension(); const payload = { @@ -224,7 +229,7 @@ export const spec = { bid: bid } } - navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, onBidderError: function (error, bidderRequest) { @@ -236,7 +241,7 @@ export const spec = { bidderRequest: bidderRequest } } - navigator.sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) + sendBeacon(EVENT_ENDPOINT, JSON.stringify(event)) }, getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { diff --git a/modules/cwireBidAdapter.md b/modules/cwireBidAdapter.md index 9804250b906..1d4f3c039c8 100644 --- a/modules/cwireBidAdapter.md +++ b/modules/cwireBidAdapter.md @@ -14,14 +14,15 @@ Prebid.js Adapter for C-Wire. Below, the list of C-WIRE params and where they can be set. -| Param name | URL parameter | AdUnit config | Type | Required | -|-------------|:-------------:|:-------------:|:--------:|:-------------:| -| pageId | | x | number | YES | -| placementId | | x | number | YES | -| cwgroups | x | | string | NO | -| cwcreative | x | | string | NO | -| cwdebug | x | | boolean | NO | -| cwfeatures | x | | string | NO | +| Param name | URL parameter | AdUnit config | Type | Required | +|-------------|:-------------:|:-------------:|:--------:|:--------:| +| pageId | | x | number | NO | +| domainId | | x | number | YES | +| placementId | | x | number | NO | +| cwgroups | x | | string | NO | +| cwcreative | x | | string | NO | +| cwdebug | x | | boolean | NO | +| cwfeatures | x | | string | NO | ### adUnit configuration @@ -38,12 +39,30 @@ var adUnits = [ } }, params: { - pageId: 1422, // required - number - placementId: 2211521, // required - number + domainId: 1422, // required - number + placementId: 2211521, // optional - number } }] } ]; +// old version for the compatibility +var adUnits = [ + { + code: 'target_div_id', // REQUIRED + bids: [{ + bidder: 'cwire', + mediaTypes: { + banner: { + sizes: [[400, 600]], + } + }, + params: { + pageId: 1422, // required - number + placementId: 2211521, // required - number + } + }] + } +]; ``` ### URL parameters diff --git a/modules/dailyhuntBidAdapter.js b/modules/dailyhuntBidAdapter.js index f96e07b71bf..fd7a5c137a7 100644 --- a/modules/dailyhuntBidAdapter.js +++ b/modules/dailyhuntBidAdapter.js @@ -129,7 +129,7 @@ const createOrtbPublisherObj = (validBidRequests) => ({ ...extractKeyInfo(validB // get bidFloor Function for different creatives function getBidFloor(bid, creative) { let floorInfo = typeof (bid.getFloor) == 'function' ? bid.getFloor({ currency: 'USD', mediaType: creative, size: '*' }) : {}; - return Math.floor(floorInfo.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0)); + return Math.floor(floorInfo?.floor || (bid.params.bidfloor ? bid.params.bidfloor : 0.0)); } const createOrtbImpObj = (bid) => { diff --git a/modules/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index afded538fd0..791fbccda5f 100644 --- a/modules/dailymotionBidAdapter.js +++ b/modules/dailymotionBidAdapter.js @@ -1,6 +1,10 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; +import { userSync } from '../src/userSync.js'; + +const DAILYMOTION_VENDOR_ID = 573; /** * Get video metadata from bid request @@ -13,10 +17,11 @@ function getVideoMetadata(bidRequest, bidderRequest) { // As per oRTB 2.5 spec, "A bid request must not contain both an App and a Site object." // See section 3.2.14 + const siteOrAppObj = deepAccess(bidderRequest, 'ortb2.site') + ? deepAccess(bidderRequest, 'ortb2.site') + : deepAccess(bidderRequest, 'ortb2.app'); // Content object is either from Object: Site or Object: App - const contentObj = deepAccess(bidderRequest, 'ortb2.site') - ? deepAccess(bidderRequest, 'ortb2.site.content') - : deepAccess(bidderRequest, 'ortb2.app.content'); + const contentObj = deepAccess(siteOrAppObj, 'content') const parsedContentData = { // Store as object keys to ensure uniqueness @@ -54,22 +59,62 @@ function getVideoMetadata(bidRequest, bidderRequest) { : Object.keys(parsedContentData.iabcat2), id: videoParams.id || deepAccess(contentObj, 'id', ''), lang: videoParams.lang || deepAccess(contentObj, 'language', ''), + livestream: typeof videoParams.livestream === 'number' + ? !!videoParams.livestream + : !!deepAccess(contentObj, 'livestream', 0), private: videoParams.private || false, tags: videoParams.tags || deepAccess(contentObj, 'keywords', ''), title: videoParams.title || deepAccess(contentObj, 'title', ''), + url: videoParams.url || deepAccess(contentObj, 'url', ''), topics: videoParams.topics || '', - xid: videoParams.xid || '', - livestream: typeof videoParams.livestream === 'number' - ? !!videoParams.livestream - : !!deepAccess(contentObj, 'livestream', 0), + isCreatedForKids: typeof videoParams.isCreatedForKids === 'boolean' + ? videoParams.isCreatedForKids + : null, + context: { + siteOrAppCat: deepAccess(siteOrAppObj, 'cat', []), + siteOrAppContentCat: deepAccess(contentObj, 'cat', []), + videoViewsInSession: ( + typeof videoParams.videoViewsInSession === 'number' && + videoParams.videoViewsInSession >= 0 + ) + ? videoParams.videoViewsInSession + : null, + autoplay: typeof videoParams.autoplay === 'boolean' + ? videoParams.autoplay + : null, + playerName: videoParams.playerName || deepAccess(contentObj, 'playerName', ''), + playerVolume: ( + typeof videoParams.playerVolume === 'number' && + videoParams.playerVolume >= 0 && + videoParams.playerVolume <= 10 + ) + ? videoParams.playerVolume + : null, + }, }; return videoMetadata; } +/** + * Check if user sync is enabled for Dailymotion + * + * @return boolean True if user sync is enabled + */ +function isUserSyncEnabled() { + const syncEnabled = deepAccess(config.getConfig('userSync'), 'syncEnabled'); + + if (!syncEnabled) return false; + + const canSyncWithIframe = userSync.canBidderRegisterSync('iframe', 'dailymotion'); + const canSyncWithPixel = userSync.canBidderRegisterSync('image', 'dailymotion'); + + return !!(canSyncWithIframe || canSyncWithPixel); +} + export const spec = { code: 'dailymotion', - gvlid: 573, + gvlid: DAILYMOTION_VENDOR_ID, supportedMediaTypes: [VIDEO], /** @@ -107,66 +152,106 @@ export const spec = { * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ - buildRequests: (validBidRequests = [], bidderRequest) => validBidRequests.map(bid => ({ - method: 'POST', - url: 'https://pb.dmxleo.com', - data: { - bidder_request: { - gdprConsent: { - apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), - consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), - // Cast boolean in any case (eg: if value is int) to ensure type - gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), - }, - refererInfo: { - page: deepAccess(bidderRequest, 'refererInfo.page', ''), + buildRequests: function(validBidRequests = [], bidderRequest) { + // check consent to be able to read user cookie + const allowCookieReading = + // No GDPR applies + !deepAccess(bidderRequest, 'gdprConsent.gdprApplies') || + // OR GDPR applies and we have global consent + deepAccess(bidderRequest, 'gdprConsent.vendorData.hasGlobalConsent') === true || + ( + // Vendor consent + deepAccess(bidderRequest, `gdprConsent.vendorData.vendor.consents.${DAILYMOTION_VENDOR_ID}`) === true && + + // Purposes with legal basis "consent". These are not flexible, so if publisher requires legitimate interest (2) it cancels them + [1, 3, 4].every(v => + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 0 && + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 2 && + deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true + ) && + + // Purposes with legal basis "legitimate interest" (default) or "consent" (when specified as such by publisher) + [2, 7, 9, 10].every(v => + deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) !== 0 && + (deepAccess(bidderRequest, `gdprConsent.vendorData.publisher.restrictions.${v}.${DAILYMOTION_VENDOR_ID}`) === 1 + ? deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.consents.${v}`) === true + : deepAccess(bidderRequest, `gdprConsent.vendorData.purpose.legitimateInterests.${v}`) === true) + ) + ); + + return validBidRequests.map(bid => ({ + method: 'POST', + url: 'https://pb.dmxleo.com', + data: { + pbv: '$prebid.version$', + bidder_request: { + gdprConsent: { + apiVersion: deepAccess(bidderRequest, 'gdprConsent.apiVersion', 1), + consentString: deepAccess(bidderRequest, 'gdprConsent.consentString', ''), + // Cast boolean in any case (eg: if value is int) to ensure type + gdprApplies: !!deepAccess(bidderRequest, 'gdprConsent.gdprApplies'), + }, + refererInfo: { + page: deepAccess(bidderRequest, 'refererInfo.page', ''), + }, + uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), + gppConsent: { + gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || + deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), + applicableSections: deepAccess(bidderRequest, 'gppConsent.applicableSections') || + deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []), + }, }, - uspConsent: deepAccess(bidderRequest, 'uspConsent', ''), - gppConsent: { - gppString: deepAccess(bidderRequest, 'gppConsent.gppString') || - deepAccess(bidderRequest, 'ortb2.regs.gpp', ''), - applicableSections: deepAccess(bidderRequest, 'gppConsent.applicableSections') || - deepAccess(bidderRequest, 'ortb2.regs.gpp_sid', []), + config: { + api_key: bid.params.apiKey, + ts: bid.params.dmTs, }, - }, - config: { - api_key: bid.params.apiKey - }, - // Cast boolean in any case (value should be 0 or 1) to ensure type - coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), - // In app context, we need to retrieve additional informations - ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { - appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), - appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), - } : {}), - request: { - adUnitCode: deepAccess(bid, 'adUnitCode', ''), - auctionId: deepAccess(bid, 'auctionId', ''), - bidId: deepAccess(bid, 'bidId', ''), - mediaTypes: { - video: { - api: bid.mediaTypes?.[VIDEO]?.api || [], - mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], - minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, - maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, - protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], - skip: bid.mediaTypes?.[VIDEO]?.skip || 0, - skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, - skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, - startdelay: bid.mediaTypes?.[VIDEO]?.startdelay || 0, - w: bid.mediaTypes?.[VIDEO]?.w || 0, - h: bid.mediaTypes?.[VIDEO]?.h || 0, + // Cast boolean in any case (value should be 0 or 1) to ensure type + coppa: !!deepAccess(bidderRequest, 'ortb2.regs.coppa'), + // In app context, we need to retrieve additional informations + ...(!deepAccess(bidderRequest, 'ortb2.site') && !!deepAccess(bidderRequest, 'ortb2.app') ? { + appBundle: deepAccess(bidderRequest, 'ortb2.app.bundle', ''), + appStoreUrl: deepAccess(bidderRequest, 'ortb2.app.storeurl', ''), + } : {}), + ...(deepAccess(bidderRequest, 'ortb2.device') ? { + device: { + lmt: deepAccess(bidderRequest, 'ortb2.device.lmt', null), + ifa: deepAccess(bidderRequest, 'ortb2.device.ifa', ''), + atts: deepAccess(bidderRequest, 'ortb2.device.ext.atts', 0), }, + } : {}), + userSyncEnabled: isUserSyncEnabled(), + request: { + adUnitCode: deepAccess(bid, 'adUnitCode', ''), + auctionId: deepAccess(bid, 'auctionId', ''), + bidId: deepAccess(bid, 'bidId', ''), + mediaTypes: { + video: { + api: bid.mediaTypes?.[VIDEO]?.api || [], + mimes: bid.mediaTypes?.[VIDEO]?.mimes || [], + minduration: bid.mediaTypes?.[VIDEO]?.minduration || 0, + maxduration: bid.mediaTypes?.[VIDEO]?.maxduration || 0, + playbackmethod: bid.mediaTypes?.[VIDEO]?.playbackmethod || [], + plcmt: bid.mediaTypes?.[VIDEO]?.plcmt, + protocols: bid.mediaTypes?.[VIDEO]?.protocols || [], + skip: bid.mediaTypes?.[VIDEO]?.skip || 0, + skipafter: bid.mediaTypes?.[VIDEO]?.skipafter || 0, + skipmin: bid.mediaTypes?.[VIDEO]?.skipmin || 0, + startdelay: bid.mediaTypes?.[VIDEO]?.startdelay, + w: bid.mediaTypes?.[VIDEO]?.w || 0, + h: bid.mediaTypes?.[VIDEO]?.h || 0, + }, + }, + sizes: bid.sizes || [], }, - sizes: bid.sizes || [], + video_metadata: getVideoMetadata(bid, bidderRequest), }, - video_metadata: getVideoMetadata(bid, bidderRequest), - }, - options: { - withCredentials: true, - crossOrigin: true, - }, - })), + options: { + withCredentials: allowCookieReading, + crossOrigin: true, + }, + })); + }, /** * Map the response from the server into a list of bids. @@ -177,6 +262,37 @@ export const spec = { * @return {Bid[]} An array of bids which were nested inside the server. */ interpretResponse: serverResponse => serverResponse?.body ? [serverResponse.body] : [], + + /** + * Retrieves user synchronization URLs based on provided options and consents. + * + * @param {object} syncOptions - Options for synchronization. + * @param {object[]} serverResponses - Array of server responses. + * @returns {object[]} - Array of synchronization URLs. + */ + getUserSyncs: (syncOptions, serverResponses) => { + if (!!serverResponses?.length && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + const iframeSyncs = []; + const pixelSyncs = []; + + serverResponses.forEach((response) => { + (response?.body?.userSyncs || []).forEach((syncUrl) => { + if (syncUrl.type === 'image') { + pixelSyncs.push({ url: syncUrl.url, type: 'image' }); + } + + if (syncUrl.type === 'iframe') { + iframeSyncs.push({ url: syncUrl.url, type: 'iframe' }); + } + }); + }); + + if (syncOptions.iframeEnabled) return iframeSyncs; + return pixelSyncs; + } + + return []; + }, }; registerBidder(spec); diff --git a/modules/dailymotionBidAdapter.md b/modules/dailymotionBidAdapter.md index 7c871b0d536..21cc6c49205 100644 --- a/modules/dailymotionBidAdapter.md +++ b/modules/dailymotionBidAdapter.md @@ -1,4 +1,4 @@ -# Overview +### Overview ``` Module Name: Dailymotion Bid Adapter @@ -6,12 +6,12 @@ Module Type: Bidder Adapter Maintainer: ad-leo-engineering@dailymotion.com ``` -# Description +### Description Dailymotion prebid adapter. Supports video ad units in instream context. -# Configuration options +### Configuration options Before calling this adapter, you need to at least set a video adUnit in an instream context and the API key in the bid parameters: @@ -36,7 +36,29 @@ const adUnits = [ `apiKey` is your publisher API key. For testing purpose, you can use "dailymotion-testing". -# Test Parameters +#### User Sync + +To enable user synchronization, add the following code. Dailymotion highly recommends using iframes and/or pixels for user syncing. This feature enhances DSP user match rates, resulting in higher bid rates and bid prices. Ensure that `pbjs.setConfig()` is called only once. + +```javascript +pbjs.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: '*', // Or add dailymotion to your list included bidders + filter: 'include' + }, + image: { + bidders: '*', // Or add dailymotion to your list of included bidders + filter: 'include' + }, + }, + }, +}); +``` + +### Test Parameters By setting the following bid parameters, you'll get a constant response to any request, to validate your adapter integration: @@ -61,12 +83,12 @@ const adUnits = [ Please note that failing to set these will result in the adapter not bidding at all. -# Sample video AdUnit +### Sample video AdUnit To allow better targeting, you should provide as much context about the video as possible. There are three ways of doing this depending on if you're using Dailymotion player or a third party one. -If you are using the Dailymotion player, you should only provide the video `xid` in your ad unit, example: +If you are using the Dailymotion player, you must provide the video `xid` in the `video.id` field of your ad unit, example: ```javascript const adUnits = [ @@ -76,7 +98,10 @@ const adUnits = [ params: { apiKey: 'dailymotion-testing', video: { - xid: 'x123456' // Dailymotion infrastructure unique video ID + id: 'x123456' // Dailymotion infrastructure unique video ID + autoplay: false, + playerName: 'dailymotion', + playerVolume: 8 }, } }], @@ -95,9 +120,9 @@ const adUnits = [ ``` This will automatically fetch the most up-to-date information about the video. -If you provide any other metadata in addition to the `xid`, they will be ignored. +Please note that if you provide any video metadata not listed above, they will be replaced by the ones fetched from the `video.id`. -If you are using a third party video player, you should not provide any `xid` and instead fill the following members: +If you are using a third party video player, you should fill the following members: ```javascript const adUnits = [ @@ -118,6 +143,11 @@ const adUnits = [ tags: 'tag_1,tag_2,tag_3', title: 'test video', topics: 'topic_1, topic_2', + isCreatedForKids: false, + videoViewsInSession: 1, + autoplay: false, + playerName: 'video.js', + playerVolume: 8 } } }], @@ -126,6 +156,12 @@ const adUnits = [ video: { api: [2, 7], context: 'instream', + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [7, 8, 11, 12, 13, 14] startdelay: 0, w: 1280, h: 720, @@ -147,10 +183,18 @@ Each of the following video metadata fields can be added in bids.params.video. * `private` - True if video is not publicly available * `tags` - Tags for the video, comma separated * `title` - Video title +* `url` - URL of the content * `topics` - Main topics for the video, comma separated -* `xid` - Dailymotion video identifier (only applicable if using the Dailymotion player) +* `isCreatedForKids` - [The content is created for children as primary audience](https://faq.dailymotion.com/hc/en-us/articles/360020920159-Content-created-for-kids) + +The following contextual information can also be added in bids.params.video. -If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will fallback to those values when possible. See the mapping below. +* `autoplay` - Playback was launched without user interaction +* `playerName` - Name of the player used to display the video +* `playerVolume` - Player volume between 0 (muted, 0%) and 10 (100%) +* `videoViewsInSession` - Number of videos viewed within the current user session + +If you already specify [First-Party data](https://docs.prebid.org/features/firstPartyData.html) through the `ortb2` object when calling [`pbjs.requestBids(requestObj)`](https://docs.prebid.org/dev-docs/publisher-api-reference/requestBids.html), we will collect the following values and fallback to bids.params.video values when applicable. See the mapping below. | From ortb2 | Metadata fields | |---------------------------------------------------------------------------------|-----------------| @@ -161,8 +205,14 @@ If you already specify [First-Party data](https://docs.prebid.org/features/first | `ortb2.site.content.livestream` | `livestream` | | `ortb2.site.content.keywords` | `tags` | | `ortb2.site.content.title` | `title` | - -# Integrating the adapter +| `ortb2.site.content.url` | `url` | +| `ortb2.app.bundle` | N/A | +| `ortb2.app.storeurl` | N/A | +| `ortb2.device.lmt` | N/A | +| `ortb2.device.ifa` | N/A | +| `ortb2.device.ext.atts` | N/A | + +### Integrating the adapter To use the adapter with any non-test request, you first need to ask an API key from Dailymotion. Please contact us through **DailymotionPrebid.js@dailymotion.com**. diff --git a/modules/datablocksBidAdapter.js b/modules/datablocksBidAdapter.js index 395706994fe..088dfd32979 100644 --- a/modules/datablocksBidAdapter.js +++ b/modules/datablocksBidAdapter.js @@ -417,7 +417,7 @@ export const spec = { // INITIATE USER SYNCING getUserSyncs: function(options, rtbResponse, gdprConsent) { const syncs = []; - let bidResponse = rtbResponse[0].body; + let bidResponse = rtbResponse?.[0]?.body ?? null; let scope = this; // LISTEN FOR SYNC DATA FROM IFRAME TYPE SYNC diff --git a/modules/datawrkzBidAdapter.js b/modules/datawrkzBidAdapter.js index b5a096521a1..2f8e397d959 100644 --- a/modules/datawrkzBidAdapter.js +++ b/modules/datawrkzBidAdapter.js @@ -227,7 +227,6 @@ function buildVideoRequest(bidRequest, bidderRequest) { maxbitrate: deepAccess(bidRequest, 'mediaTypes.video.maxbitrate'), delivery: deepAccess(bidRequest, 'mediaTypes.video.delivery'), linearity: deepAccess(bidRequest, 'mediaTypes.video.linearity'), - placement: deepAccess(bidRequest, 'mediaTypes.video.placement'), skip: deepAccess(bidRequest, 'mediaTypes.video.skip'), skipafter: deepAccess(bidRequest, 'mediaTypes.video.skipafter') }; diff --git a/modules/debugging/bidInterceptor.js b/modules/debugging/bidInterceptor.js index 3afaacaeb81..102d1723eec 100644 --- a/modules/debugging/bidInterceptor.js +++ b/modules/debugging/bidInterceptor.js @@ -1,9 +1,9 @@ import { deepAccess, deepClone, - deepEqual, delayExecution, - mergeDeep + mergeDeep, + hasNonSerializableProperty } from '../../src/utils.js'; /** @@ -22,9 +22,9 @@ Object.assign(BidInterceptor.prototype, { }, serializeConfig(ruleDefs) { const isSerializable = (ruleDef, i) => { - const serializable = deepEqual(ruleDef, JSON.parse(JSON.stringify(ruleDef)), {checkTypes: true}); + const serializable = !hasNonSerializableProperty(ruleDef); if (!serializable && !deepAccess(ruleDef, 'options.suppressWarnings')) { - this.logger.logWarn(`Bid interceptor rule definition #${i + 1} is not serializable and will be lost after a refresh. Rule definition: `, ruleDef); + this.logger.logWarn(`Bid interceptor rule definition #${i + 1} contains non-serializable properties and will be lost after a refresh. Rule definition: `, ruleDef); } return serializable; } @@ -152,10 +152,17 @@ Object.assign(BidInterceptor.prototype, { }, paapiReplacer(paapiDef, ruleNo) { + function wrap(configs = []) { + return configs.map(config => { + return Object.keys(config).some(k => !['config', 'igb'].includes(k)) + ? {config} + : config + }); + } if (Array.isArray(paapiDef)) { - return () => paapiDef; + return () => wrap(paapiDef); } else if (typeof paapiDef === 'function') { - return paapiDef + return (...args) => wrap(paapiDef(...args)) } else { this.logger.logError(`Invalid 'paapi' definition for debug bid interceptor (in rule #${ruleNo})`); } diff --git a/modules/debugging/debugging.js b/modules/debugging/debugging.js index 2fd1731dc4e..803d7ee5cd7 100644 --- a/modules/debugging/debugging.js +++ b/modules/debugging/debugging.js @@ -31,6 +31,7 @@ export function disableDebugging({hook, logger}) { } } +// eslint-disable-next-line prebid/no-global function saveDebuggingConfig(debugConfig, {sessionStorage = window.sessionStorage, DEBUG_KEY} = {}) { if (!debugConfig.enabled) { try { @@ -49,6 +50,7 @@ function saveDebuggingConfig(debugConfig, {sessionStorage = window.sessionStorag } } +// eslint-disable-next-line prebid/no-global export function getConfig(debugging, {getStorage = () => window.sessionStorage, DEBUG_KEY, config, hook, logger} = {}) { if (debugging == null) return; let sessionStorage; @@ -70,6 +72,7 @@ export function getConfig(debugging, {getStorage = () => window.sessionStorage, export function sessionLoader({DEBUG_KEY, storage, config, hook, logger}) { let overrides; try { + // eslint-disable-next-line prebid/no-global storage = storage || window.sessionStorage; overrides = JSON.parse(storage.getItem(DEBUG_KEY)); } catch (e) { @@ -102,11 +105,13 @@ export function bidderBidInterceptor(next, interceptBids, spec, bids, bidRequest ({bids, bidRequest} = interceptBids({ bids, bidRequest, - addBid: cbs.onBid, - addPaapiConfig: (config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, config}), + addBid: wrapCallback(cbs.onBid), + addPaapiConfig: wrapCallback((config, bidRequest) => cbs.onPaapi({bidId: bidRequest.bidId, ...config})), done })); if (bids.length === 0) { + // eslint-disable-next-line no-unused-expressions + cbs.onResponse?.({}); // trigger onResponse so that the bidder may be marked as "timely" if necessary done(); } else { next(spec, bids, bidRequest, ajax, wrapCallback, {...cbs, onCompletion: done}); diff --git a/modules/debugging/pbsInterceptor.js b/modules/debugging/pbsInterceptor.js index 5c2eb458b18..c514031ad0e 100644 --- a/modules/debugging/pbsInterceptor.js +++ b/modules/debugging/pbsInterceptor.js @@ -29,7 +29,7 @@ export function makePbsInterceptor({createBid}) { adUnitCode: bidRequest.adUnitCode, ortb2: bidderRequest.ortb2, ortb2Imp: bidRequest.ortb2Imp, - config + ...config }) }, done diff --git a/modules/deepintentBidAdapter.js b/modules/deepintentBidAdapter.js index 0a64ed88ca5..9c67eb02fa9 100644 --- a/modules/deepintentBidAdapter.js +++ b/modules/deepintentBidAdapter.js @@ -1,35 +1,17 @@ -import { generateUUID, deepSetValue, deepAccess, isArray, isInteger, logError, logWarn } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import { generateUUID, deepSetValue, deepAccess, isArray, isFn, isPlainObject, logError, logWarn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { COMMON_ORTB_VIDEO_PARAMS, formatResponse } from '../libraries/deepintentUtils/index.js'; const BIDDER_CODE = 'deepintent'; const GVL_ID = 541; const BIDDER_ENDPOINT = 'https://prebid.deepintent.com/prebid'; const USER_SYNC_URL = 'https://cdn.deepintent.com/syncpixel.html'; const DI_M_V = '1.0.0'; export const ORTB_VIDEO_PARAMS = { - 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), - 'placement': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + ...COMMON_ORTB_VIDEO_PARAMS, + 'plcmt': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 5), 'delivery': (value) => [1, 2, 3].indexOf(value) !== -1, 'pos': (value) => [0, 1, 2, 3, 4, 5, 6, 7].indexOf(value) !== -1, - 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) }; export const spec = { code: BIDDER_CODE, @@ -155,29 +137,13 @@ function clean(obj) { } } -function formatResponse(bid) { - return { - requestId: bid && bid.impid ? bid.impid : undefined, - cpm: bid && bid.price ? bid.price : 0.0, - 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', - ttl: 300, - dealId: bid && bid.dealId ? bid.dealId : undefined - } -} - function buildImpression(bid) { let impression = {}; + const floor = getFloor(bid); impression = { id: bid.bidId, tagid: bid.params.tagId || '', + ...(!isNaN(floor) && { bidfloor: floor }), secure: window.location.protocol === 'https:' ? 1 : 0, displaymanager: 'di_prebid', displaymanagerver: DI_M_V, @@ -192,6 +158,23 @@ function buildImpression(bid) { return impression; } +function getFloor(bidRequest) { + if (!isFn(bidRequest.getFloor)) { + return bidRequest.params?.bidfloor; + } + + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + + if (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === 'USD') { + return floor.floor; + } + return null; +} + function _buildVideo(bid) { const videoObj = {}; const videoAdUnitParams = deepAccess(bid, 'mediaTypes.video', {}); diff --git a/modules/deepintentBidAdapter.md b/modules/deepintentBidAdapter.md index 84c375d69a4..73f1918085e 100644 --- a/modules/deepintentBidAdapter.md +++ b/modules/deepintentBidAdapter.md @@ -15,64 +15,70 @@ Module that connects to Deepintent's demand sources. # Banner Test Request ``` var adUnits = [ - { - code: 'di_adUnit1', - mediaTypes: { - banner: { - sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet - } - } - bids: [ - { - bidder: 'deepintent', - params: { - tagId: '1300', // Required parameter - w: 300, // Width and Height here will override sizes in mediatype - h: 250, - pos: 1, - custom: { // Custom parameters in form of key value pairs - user_min_age: 18 - } - } - } - ] - } - ]; + { + code: 'di_adUnit1', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size, only first one will be picked up since multiple ad sizes are not supported yet + }, + }, + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter + bidfloor: 1.5, // optional + w: 300, // Width and Height here will override sizes in mediatype + h: 250, + pos: 1, + custom: { + // Custom parameters in form of key value pairs + user_min_age: 18, + }, + }, + }, + ], + }, + ]; ``` # Sample Video Ad Unit ``` -var adVideoAdUnits = [ -{ - code: 'test-div-video', - mediaTypes: { - video: { - playerSize: [640, 480], // required - context: 'instream' //required - } - }, - bids: [{ - bidder: 'deepintent', - params: { - tagId: '1300', // Required parameter // required - video: { - mimes: ['video/mp4','video/x-flv'], // required - skippable: true, // optional - minduration: 5, // optional - maxduration: 30, // optional - startdelay: 5, // optional - playbackmethod: [1,3], // optional - api: [ 1, 2 ], // optional - protocols: [ 2, 3 ], // optional - battr: [ 13, 14 ], // optional - linearity: 1, // optional - placement: 2, // optional - minbitrate: 10, // optional - maxbitrate: 10 // optional - } - } - }] -}] + var adVideoAdUnits = [ + { + code: 'test-div-video', + mediaTypes: { + video: { + playerSize: [640, 480], // required + context: 'instream', //required + }, + }, + bids: [ + { + bidder: 'deepintent', + params: { + tagId: '1300', // Required parameter // required + bidfloor: 1.5, // optional + video: { + mimes: ['video/mp4', 'video/x-flv'], // required + skippable: true, // optional + minduration: 5, // optional + maxduration: 30, // optional + startdelay: 5, // optional + playbackmethod: [1, 3], // optional + api: [1, 2], // optional + protocols: [2, 3], // optional + battr: [13, 14], // optional + linearity: 1, // optional + plcmt: 2, // optional + minbitrate: 10, // optional + maxbitrate: 10, // optional + }, + }, + }, + ], + }, + ]; ``` ###Recommended User Sync Configuration @@ -84,6 +90,4 @@ pbjs.setConfig({ enabledBidders: ['deepintent'], syncDelay: 3000 }}); - - ``` diff --git a/modules/deltaprojectsBidAdapter.js b/modules/deltaprojectsBidAdapter.js index 870378a13dd..bf5489322c9 100644 --- a/modules/deltaprojectsBidAdapter.js +++ b/modules/deltaprojectsBidAdapter.js @@ -8,7 +8,8 @@ import { isFn, isNumber, logError, - logWarn + logWarn, + setOnAny } from '../src/utils.js'; import {config} from '../src/config.js'; @@ -228,22 +229,12 @@ export function getBidFloor(bid, mediaType, size, currency) { if (isFn(bid.getFloor)) { const bidFloorCurrency = currency || 'USD'; const bidFloor = bid.getFloor({currency: bidFloorCurrency, mediaType: mediaType, size: size}); - if (isNumber(bidFloor.floor)) { + if (isNumber(bidFloor?.floor)) { return bidFloor; } } } -/** -- Helper methods -- */ -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - /** -- Register -- */ export const spec = { code: BIDDER_CODE, diff --git a/modules/dexertoBidAdapter.js b/modules/dexertoBidAdapter.js new file mode 100644 index 00000000000..af06341e9e6 --- /dev/null +++ b/modules/dexertoBidAdapter.js @@ -0,0 +1,31 @@ +import { + BANNER +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const ENDPOINT_URL = 'https://rtb.dexerto.media/hb/dexerto'; +// Export const spec +export const spec = { + code: 'dexerto', + supportedMediaTypes: BANNER, + // Determines whether or not the given bid request is valid + isBidRequestValid: (bid) => { + return !!(bid.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: (bidRequests, bidderRequest) => { + return getBannerRequest(bidRequests, bidderRequest, ENDPOINT_URL); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: (bidResponse, bidRequest) => { + return getBannerResponse(bidResponse, BANNER); + } +} + +registerBidder(spec); diff --git a/modules/dexertoBidAdapter.md b/modules/dexertoBidAdapter.md new file mode 100644 index 00000000000..ad4d7bb42eb --- /dev/null +++ b/modules/dexertoBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Dexerto Bidder Adapter +Module Type: Bidder Adapter +Maintainer: niels.claes@dexerto.com +``` + +# Description + +Dexerto currently supports the BANNER type ads through prebid js + +Module that connects to dexerto's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'dexerto', + params: { + placement_id: 110003, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/modules/dfpAdServerVideo.js b/modules/dfpAdServerVideo.js index abf58aceb45..3e1a716d8e7 100644 --- a/modules/dfpAdServerVideo.js +++ b/modules/dfpAdServerVideo.js @@ -2,29 +2,27 @@ * This module adds [DFP support]{@link https://www.doubleclickbygoogle.com/} for Video to Prebid. */ -import {registerVideoSupport} from '../src/adServerManager.js'; -import {targeting} from '../src/targeting.js'; +import { getSignals } from '../libraries/gptUtils/gptUtils.js'; +import { registerVideoSupport } from '../src/adServerManager.js'; +import { getPPID } from '../src/adserver.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { EVENTS } from '../src/constants.js'; +import * as events from '../src/events.js'; +import { getHook } from '../src/hook.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { targeting } from '../src/targeting.js'; import { - isNumber, buildUrl, deepAccess, formatQS, isEmpty, + isNumber, logError, parseSizesInput, - parseUrl, - uniques + parseUrl } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook, submodule} from '../src/hook.js'; -import {auctionManager} from '../src/auctionManager.js'; -import {gdprDataHandler} from '../src/adapterManager.js'; -import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; -import {getPPID} from '../src/adserver.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; - +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; /** * @typedef {Object} DfpVideoParams * @@ -54,16 +52,6 @@ import {CLIENT_SECTIONS} from '../src/fpd/oneClient.js'; * @param {string} [url] video adserver url */ -/** Safe defaults which work on pretty much all video calls. */ -const defaultParamConstants = { - env: 'vp', - gdfp_req: 1, - output: 'vast', - unviewed_position_start: 1, -}; - -export const adpodUtils = {}; - export const dep = { ri: getRefererInfo } @@ -115,21 +103,16 @@ export function buildDfpVideoUrl(options) { let encodedCustomParams = getCustParams(bid, options, urlSearchComponent && urlSearchComponent.cust_params); const queryParams = Object.assign({}, - defaultParamConstants, + DEFAULT_DFP_PARAMS, urlComponents.search, derivedParams, options.params, - { cust_params: encodedCustomParams } + { cust_params: encodedCustomParams }, + gdprParams() ); 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; } - } if (!queryParams.ppid) { const ppid = getPPID(); @@ -181,20 +164,7 @@ export function buildDfpVideoUrl(options) { const fpd = auctionManager.index.getBidRequest(options.bid || {})?.ortb2 ?? auctionManager.index.getAuction(options.bid || {})?.getFPD()?.global; - function getSegments(sections, segtax) { - return sections - .flatMap(section => deepAccess(fpd, section) || []) - .filter(datum => datum.ext?.segtax === segtax) - .flatMap(datum => datum.segment?.map(seg => seg.id)) - .filter(ob => ob) - .filter(uniques) - } - - const signals = Object.entries({ - IAB_AUDIENCE_1_1: getSegments(['user.data'], 4), - IAB_CONTENT_2_2: getSegments(CLIENT_SECTIONS.map(section => `${section}.content.data`), 6) - }).map(([taxonomy, values]) => values.length ? {taxonomy, values} : null) - .filter(ob => ob); + const signals = getSignals(fpd); if (signals.length) { queryParams.ppsj = btoa(JSON.stringify({ @@ -202,11 +172,7 @@ export function buildDfpVideoUrl(options) { })) } - return buildUrl(Object.assign({ - protocol: 'https', - host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads' - }, urlComponents, { search: queryParams })); + return buildUrl(Object.assign({}, DFP_ENDPOINT, urlComponents, { search: queryParams })); } export function notifyTranslationModule(fn) { @@ -215,100 +181,11 @@ export function notifyTranslationModule(fn) { if (config.getConfig('brandCategoryTranslation.translationFile')) { getHook('registerAdserver').before(notifyTranslationModule); } -/** - * @typedef {Object} DfpAdpodOptions - * - * @param {string} code Ad Unit code - * @param {Object} params Query params which should be set on the DFP request. - * These will override this module's defaults whenever they conflict. - * @param {function} callback Callback function to execute when master tag is ready - */ - -/** - * Creates master tag url for long-form - * @param {DfpAdpodOptions} options - * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP - */ -export function buildAdpodVideoUrl({code, params, callback} = {}) { - // TODO: the public API for this does not take in enough info to fill all DFP params (adUnit/bid), - // and is marked "alpha": https://docs.prebid.org/dev-docs/publisher-api-reference/adServers.dfp.buildAdpodVideoUrl.html - if (!params || !callback) { - logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); - return; - } - - const derivedParams = { - correlator: Date.now(), - sz: getSizeForAdUnit(code), - url: encodeURIComponent(location.href), - }; - - function getSizeForAdUnit(code) { - let adUnit = auctionManager.getAdUnits() - .filter((adUnit) => adUnit.code === code) - let sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); - return parseSizesInput(sizes).join('|'); - } - - adpodUtils.getTargeting({ - 'codes': [code], - 'callback': createMasterTag - }); - - function createMasterTag(err, targeting) { - if (err) { - callback(err, null); - return; - } - - let initialValue = { - [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, - [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined - }; - let customParams = {}; - if (targeting[code]) { - customParams = targeting[code].reduce((acc, curValue) => { - if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { - acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; - } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { - acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] - } - return acc; - }, initialValue); - } - - let encodedCustomParams = encodeURIComponent(formatQS(customParams)); - - const queryParams = Object.assign({}, - defaultParamConstants, - derivedParams, - params, - { 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 masterTag = buildUrl({ - protocol: 'https', - host: 'securepubads.g.doubleclick.net', - pathname: '/gampad/ads', - search: queryParams - }); - - callback(null, masterTag); - } -} - /** * Builds a video url from a base dfp video url and a winning bid, appending * Prebid-specific key-values. * @param {Object} components base video adserver url parsed into components object - * @param {AdapterBidResponse} bid winning bid object to append parameters from + * @param {Object} bid winning bid object to append parameters from * @param {Object} options Options which should be used to construct the URL (used for custom params). * @return {string} video url */ @@ -325,7 +202,7 @@ function buildUrlFromAdserverUrlComponents(components, bid, options) { /** * Returns the encoded vast url if it exists on a bid object, only if prebid-cache * is disabled, and description_url is not already set on a given input - * @param {AdapterBidResponse} bid object to check for vast url + * @param {Object} bid object to check for vast url * @param {Object} components the object to check that description_url is NOT set on * @param {string} prop the property of components that would contain description_url * @return {string | undefined} The encoded vast url if it exists, or undefined @@ -336,7 +213,7 @@ function getDescriptionUrl(bid, components, prop) { /** * Returns the encoded `cust_params` from the bid.adserverTargeting and adds the `hb_uuid`, and `hb_cache_id`. Optionally the options.params.cust_params - * @param {AdapterBidResponse} bid + * @param {Object} bid * @param {Object} options this is the options passed in from the `buildDfpVideoUrl` function * @return {Object} Encoded key value pairs for cust_params */ @@ -375,8 +252,4 @@ function getCustParams(bid, options, urlCustParams) { registerVideoSupport('dfp', { buildVideoUrl: buildDfpVideoUrl, - buildAdpodVideoUrl: buildAdpodVideoUrl, - getAdpodTargeting: (args) => adpodUtils.getTargeting(args) }); - -submodule('adpod', adpodUtils); diff --git a/modules/dfpAdpod.js b/modules/dfpAdpod.js new file mode 100644 index 00000000000..d443e770d87 --- /dev/null +++ b/modules/dfpAdpod.js @@ -0,0 +1,95 @@ +import {submodule} from '../src/hook.js'; +import {buildUrl, deepAccess, formatQS, logError, parseSizesInput} from '../src/utils.js'; +import {auctionManager} from '../src/auctionManager.js'; +import {DEFAULT_DFP_PARAMS, DFP_ENDPOINT, gdprParams} from '../libraries/dfpUtils/dfpUtils.js'; +import {registerVideoSupport} from '../src/adServerManager.js'; + +export const adpodUtils = {}; + +/** + * @typedef {Object} DfpAdpodOptions + * + * @param {string} code Ad Unit code + * @param {Object} params Query params which should be set on the DFP request. + * These will override this module's defaults whenever they conflict. + * @param {function} callback Callback function to execute when master tag is ready + */ + +/** + * Creates master tag url for long-form + * @param {DfpAdpodOptions} options + * @returns {string} A URL which calls DFP with custom adpod targeting key values to compete with rest of the demand in DFP + */ +export function buildAdpodVideoUrl({code, params, callback} = {}) { + // TODO: the public API for this does not take in enough info to fill all DFP params (adUnit/bid), + // and is marked "alpha": https://docs.prebid.org/dev-docs/publisher-api-reference/adServers.dfp.buildAdpodVideoUrl.html + if (!params || !callback) { + logError(`A params object and a callback is required to use pbjs.adServers.dfp.buildAdpodVideoUrl`); + return; + } + + const derivedParams = { + correlator: Date.now(), + sz: getSizeForAdUnit(code), + url: encodeURIComponent(location.href), + }; + + function getSizeForAdUnit(code) { + let adUnit = auctionManager.getAdUnits() + .filter((adUnit) => adUnit.code === code) + let sizes = deepAccess(adUnit[0], 'mediaTypes.video.playerSize'); + return parseSizesInput(sizes).join('|'); + } + + adpodUtils.getTargeting({ + 'codes': [code], + 'callback': createMasterTag + }); + + function createMasterTag(err, targeting) { + if (err) { + callback(err, null); + return; + } + + let initialValue = { + [adpodUtils.TARGETING_KEY_PB_CAT_DUR]: undefined, + [adpodUtils.TARGETING_KEY_CACHE_ID]: undefined + }; + let customParams = {}; + if (targeting[code]) { + customParams = targeting[code].reduce((acc, curValue) => { + if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_PB_CAT_DUR) { + acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] = (typeof acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] !== 'undefined') ? acc[adpodUtils.TARGETING_KEY_PB_CAT_DUR] + ',' + curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR] : curValue[adpodUtils.TARGETING_KEY_PB_CAT_DUR]; + } else if (Object.keys(curValue)[0] === adpodUtils.TARGETING_KEY_CACHE_ID) { + acc[adpodUtils.TARGETING_KEY_CACHE_ID] = curValue[adpodUtils.TARGETING_KEY_CACHE_ID] + } + return acc; + }, initialValue); + } + + let encodedCustomParams = encodeURIComponent(formatQS(customParams)); + + const queryParams = Object.assign({}, + DEFAULT_DFP_PARAMS, + derivedParams, + params, + { cust_params: encodedCustomParams }, + gdprParams(), + ); + + const masterTag = buildUrl({ + ...DFP_ENDPOINT, + search: queryParams + }); + + callback(null, masterTag); + } +} + +registerVideoSupport('dfp', { + buildAdpodVideoUrl: buildAdpodVideoUrl, + getAdpodTargeting: (args) => adpodUtils.getTargeting(args) +}); + +submodule('adpod', adpodUtils); diff --git a/modules/dgkeywordRtdProvider.js b/modules/dgkeywordRtdProvider.js index 14519ae2713..c97296f6982 100644 --- a/modules/dgkeywordRtdProvider.js +++ b/modules/dgkeywordRtdProvider.js @@ -101,6 +101,8 @@ export function getProfileApiUrl(customeUrl, enableReadFpid) { export function readFpidFromLocalStrage() { try { + // TODO: use storageManager + // eslint-disable-next-line prebid/no-global const fpid = window.localStorage.getItem('ope_fpid'); if (fpid) { return fpid; diff --git a/modules/dianomiBidAdapter.js b/modules/dianomiBidAdapter.js index d4b2a4a5da5..777878b5207 100644 --- a/modules/dianomiBidAdapter.js +++ b/modules/dianomiBidAdapter.js @@ -10,10 +10,12 @@ import { parseSizesInput, deepSetValue, formatQS, + setOnAny } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const { getConfig } = config; @@ -126,8 +128,8 @@ export const spec = { currency: currency || 'USD', }) : {}; - const bidfloor = floorInfo.floor; - const bidfloorcur = floorInfo.currency; + const bidfloor = floorInfo?.floor; + const bidfloorcur = floorInfo?.currency; const { smartadId } = bid.params; const imp = { @@ -301,19 +303,8 @@ export const spec = { .filter(Boolean); }, getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent) => { - const params = {}; - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } + const params = getUserSyncParams(gdprConsent, uspConsent); - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } if (syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint return { @@ -355,15 +346,6 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - function flatten(arr) { return [].concat(...arr); } diff --git a/modules/digitalMatterBidAdapter.js b/modules/digitalMatterBidAdapter.js new file mode 100644 index 00000000000..77df1af3886 --- /dev/null +++ b/modules/digitalMatterBidAdapter.js @@ -0,0 +1,210 @@ +import {deepAccess, deepSetValue, getDNT, inIframe, logWarn, parseSizesInput} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER} from '../src/mediaTypes.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; + +const BIDDER_CODE = 'digitalMatter'; +const GVLID = 1345; +const ENDPOINT_URL = 'https://adx.digitalmatter.services/' + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER], + aliases: ['dichange', 'digitalmatter'], + bidParameters: ['accountId', 'siteId'], + isBidRequestValid: function (bid) { + if (typeof bid.params !== 'object') { + return false; + } + if (!hasBannerMediaType(bid)) { + logWarn('Invalid bid request: missing required mediaType - banner'); + return false; + } + + return !!(bid.params.accountId && bid.params.siteId) + }, + buildRequests: function (validBidRequests, bidderRequest) { + const common = bidderRequest.ortb2 || {}; + const site = common.site; + const tid = common?.source?.tid; + const {user} = common || {}; + + if (!site.page) { + site.page = bidderRequest.refererInfo.page; + } + + const device = getDevice(common.device); + const schain = getByKey(validBidRequests, 'schain'); + const eids = getByKey(validBidRequests, 'userIdAsEids'); + const currency = config.getConfig('currency') + const cur = currency && [currency]; + + const imp = validBidRequests.map((bid, id) => { + const {accountId, siteId} = bid.params; + const bannerParams = deepAccess(bid, 'mediaTypes.banner'); + const position = deepAccess(bid, 'mediaTypes.banner.pos') ?? 0; + + return { + id: bid.adUnitCode, + bidId: bid.bidId, + accountId: accountId, + adUnitCode: bid.adUnitCode, + siteId: siteId, + banner: { + pos: position, + topframe: inIframe() ? 0 : 1, + format: bannerParams.sizes.map(sizeArr => ({ + w: sizeArr[0], + h: sizeArr[1] + })) + }, + sizes: parseSizesInput(bannerParams.sizes), + }; + }); + + const ext = { + prebid: { + targeting: { + includewinners: true, + includebidderkeys: false + } + } + }; + + const payload = { + id: bidderRequest.bidderRequestId, + tid, + site, + device, + user, + cur, + imp, + test: config.getConfig('debug') ? 1 : 0, + tmax: bidderRequest.timeout, + start: bidderRequest.auctionStart, + ext + }; + + if (schain) { + deepSetValue(payload, 'source.ext.schain', schain); + } + + if (eids) { + deepSetValue(payload, 'user.ext.eids', eids); + } + + if (deepAccess(bidderRequest, 'gdprConsent.gdprApplies') !== undefined) { + deepSetValue(payload, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(payload, 'regs.ext.gdpr', bidderRequest.gdprConsent.gdprApplies & 1); + } + + const payloadString = JSON.stringify(payload); + return { + method: 'POST', + url: ENDPOINT_URL + 'openrtb2/auction', + data: payloadString, + }; + }, + interpretResponse: function (serverResponse) { + const body = serverResponse.body || serverResponse; + const {cur} = body; + const bids = []; + + if (body && body.bids && Array.isArray(body.bids)) { + body.bids.forEach(bidItem => { + const bid = { + requestId: bidItem.bidid, + adomain: bidItem.adomain, + cpm: bidItem.cpm, + currency: cur, + netRevenue: true, + ttl: bidItem.ttl || 300, + creativeId: bidItem.creativeid, + width: bidItem.width, + height: bidItem.height, + dealId: bidItem.dealid, + ad: bidItem.ad, + meta: bidItem.meta, + }; + + bids.push(bid); + }); + } + + return bids + }, + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if (usersSynced) { + return []; + } + + const userSyncs = []; + + function checkGppStatus(gppConsent) { + if (gppConsent && Array.isArray(gppConsent.applicableSections)) { + return gppConsent.applicableSections.every(sec => typeof sec === 'number' && sec <= 5); + } + return true; + } + + if (hasPurpose1Consent(gdprConsent) && checkGppStatus(gppConsent)) { + responses.forEach(response => { + if (response.body.ext && response.body.ext.usersync) { + try { + const userSync = response.body.ext.usersync; + + userSync.forEach((element) => { + let url = element.url; + let type = element.type; + + if (url) { + if ((type === 'image' || type === 'redirect') && syncOptions.pixelEnabled) { + userSyncs.push({type: 'image', url: url}); + } else if (type === 'iframe' && syncOptions.iframeEnabled) { + userSyncs.push({type: 'iframe', url: url}); + } + } + }) + } catch (e) { + // + } + } + }); + } + + return userSyncs; + } +} + +let usersSynced = false; + +function hasBannerMediaType(bidRequest) { + return !!deepAccess(bidRequest, 'mediaTypes.banner'); +} + +function getDevice(data) { + let dnt = data.dnt; + if (!dnt) { + dnt = getDNT() ? 1 : 0; + } + return { + w: data.w || window.innerWidth, + h: data.h || window.innerHeight, + ua: data.ua || navigator.userAgent, + dnt: dnt, + language: data.language || navigator.language, + } +} + +function getByKey(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } +} + +registerBidder(spec); diff --git a/modules/digitalMatterBidAdapter.md b/modules/digitalMatterBidAdapter.md new file mode 100644 index 00000000000..19c0ecd631a --- /dev/null +++ b/modules/digitalMatterBidAdapter.md @@ -0,0 +1,38 @@ +# Overview + +``` +Module Name: Digital Matter Bid Adapter +Module Type: Digital Matter Bid Adapter +Maintainer: prebid@digitalmatter.ai +``` + +# Description + +Module that connects to Digital Matter demand sources + +# Banner Test Parameters + +```js +var adUnits = [ + { + code: "test-banner", + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + } + }, + bids: [ + { + bidder: "digitalMatter", + params: { + accountId: "1_demo_1", // string, required + siteId: "1-demo-1" // string, required + } + } + ] + } +]; +``` diff --git a/modules/discoveryBidAdapter.js b/modules/discoveryBidAdapter.js index 3ac905ef6b5..0dbb1a3a6cb 100644 --- a/modules/discoveryBidAdapter.js +++ b/modules/discoveryBidAdapter.js @@ -2,6 +2,11 @@ import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink, getReferrer } from '../libraries/fpdUtils/pageInfo.js'; +import { getDevice, getScreenSize } from '../libraries/fpdUtils/deviceInfo.js'; +import { getBidFloor } from '../libraries/currencyUtils/floor.js'; +import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -21,6 +26,9 @@ const MEDIATYPE = [BANNER, NATIVE]; const COOKIE_KEY_SSPPID = '_ss_pp_id'; export const COOKIE_KEY_MGUID = '__mguid_'; const COOKIE_KEY_PMGUID = '__pmguid_'; +const COOKIE_KEY_PBUID = 'pub_pp_tag'; +const STORAGE_KEY_FTUID = 'fluct_ppUUIDv4'; +const STORAGE_KEY_IMUID = '__im_ppid'; const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year const COOKY_SYNC_IFRAME_URL = 'https://asset.popin.cc/js/cookieSync.html'; export const THIRD_PARTY_COOKIE_ORIGIN = 'https://asset.popin.cc'; @@ -67,64 +75,6 @@ const NATIVERET = { ext: {}, }; -/** - * get page title111 - * @returns {string} - */ - -export function getPageTitle(win = window) { - try { - const ogTitle = win.top.document.querySelector('meta[property="og:title"]') - return win.top.document.title || (ogTitle && ogTitle.content) || ''; - } catch (e) { - const ogTitle = document.querySelector('meta[property="og:title"]') - return document.title || (ogTitle && ogTitle.content) || ''; - } -} - -/** - * get page description - * @returns {string} - */ -export function getPageDescription(win = window) { - let element; - - try { - element = win.top.document.querySelector('meta[name="description"]') || - win.top.document.querySelector('meta[property="og:description"]') - } catch (e) { - element = document.querySelector('meta[name="description"]') || - document.querySelector('meta[property="og:description"]') - } - - return (element && element.content) || ''; -} - -/** - * get page keywords - * @returns {string} - */ -export function getPageKeywords(win = window) { - let element; - - try { - element = win.top.document.querySelector('meta[name="keywords"]'); - } catch (e) { - element = document.querySelector('meta[name="keywords"]'); - } - - return (element && element.content) || ''; -} - -/** - * get connection downlink - * @returns {number} - */ -export function getConnectionDownLink(win = window) { - const nav = win.navigator || {}; - return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; -} - /** * get pmg uid * 获取并生成用户的id @@ -139,7 +89,7 @@ export const getPmgUID = () => { } // Extend the expiration time of pmguid try { - storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCurrentTimeToUTCString()); + storage.setCookie(COOKIE_KEY_PMGUID, pmgUid, getCookieTimeToUTCString()); } catch (e) {} return pmgUid; }; @@ -165,150 +115,14 @@ function getKv(obj, ...keys) { return o; } -/** - * get device - * @return {boolean} - */ -function getDevice() { - let check = false; - (function (a) { - let reg1 = new RegExp( - [ - '(android|bbd+|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|android|ipad|playbook|silk', - ].join(''), - 'i' - ); - let reg2 = new RegExp( - [ - '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-', - ].join(''), - 'i' - ); - if (reg1.test(a) || reg2.test(a.substr(0, 4))) { - check = true; - } - })(navigator.userAgent || navigator.vendor || window.opera); - return check; -} - -/** - * get BidFloor - * @param {*} bid - * @param {*} mediaType - * @param {*} sizes - * @returns - */ -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; - } -} - -/** - * get sizes for rtb - * @param {Array|Object} requestSizes - * @return {Object} - */ -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; -} - // Support sizes -const popInAdSize = [ - { w: 300, h: 250 }, - { w: 300, h: 600 }, - { w: 728, h: 90 }, - { w: 970, h: 250 }, - { w: 320, h: 50 }, - { w: 160, h: 600 }, - { w: 320, h: 180 }, - { w: 320, h: 100 }, - { w: 336, h: 280 }, -]; +const popInAdSize = normalAdSize; /** - * get screen size - * - * @returns {Array} eg: "['widthxheight']" - */ -function getScreenSize() { - return utils.parseSizesInput([window.screen.width, window.screen.height]); -} - -/** - * @param {BidRequest} bidRequest - * @param bidderRequest - * @returns {string} - */ -function getReferrer(bidRequest = {}, bidderRequest = {}) { - let pageUrl; - if (bidRequest.params && bidRequest.params.referrer) { - pageUrl = bidRequest.params.referrer; - } else { - pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page'); - } - return pageUrl; -} - -/** - * get current time to UTC string + * get cookie time to UTC string * @returns utc string */ -export function getCurrentTimeToUTCString() { +export function getCookieTimeToUTCString() { const date = new Date(); date.setTime(date.getTime() + COOKIE_RETENTION_TIME); return date.toUTCString(); @@ -381,9 +195,8 @@ function getItems(validBidRequests, bidderRequest) { } } if (!matchSize) { - matchSize = sizes[0] - ? { h: sizes[0].height || 0, w: sizes[0].width || 0 } - : { h: 0, w: 0 }; + const { height = 0, width = 0 } = sizes[0] || {}; + matchSize = { h: height, w: width }; } ret = { id: id, @@ -423,7 +236,7 @@ export const buildUTMTagData = (url) => { }); UTMValue = JSON.parse(storage.getCookie(UTM_KEY) || '{}'); Object.assign(UTMValue, UTMParams); - storage.setCookie(UTM_KEY, JSON.stringify(UTMValue), getCurrentTimeToUTCString()); + storage.setCookie(UTM_KEY, JSON.stringify(UTMValue), getCookieTimeToUTCString()); } /** @@ -434,10 +247,10 @@ export const buildUTMTagData = (url) => { * @return {Object} */ function getParam(validBidRequests, bidderRequest) { - const pubcid = utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); const sharedid = utils.deepAccess(validBidRequests[0], 'userId.sharedid.id') || - utils.deepAccess(validBidRequests[0], 'userId.pubcid'); + utils.deepAccess(validBidRequests[0], 'userId.pubcid') || + utils.deepAccess(validBidRequests[0], 'crumbs.pubcid'); const eids = validBidRequests[0].userIdAsEids || validBidRequests[0].userId; let isMobile = getDevice() ? 1 : 0; @@ -455,11 +268,32 @@ function getParam(validBidRequests, bidderRequest) { const referer = utils.deepAccess(bidderRequest, 'refererInfo.ref'); const firstPartyData = bidderRequest.ortb2; const tpData = utils.deepAccess(bidderRequest, 'ortb2.user.data') || undefined; - const topWindow = window.top; const title = getPageTitle(); const desc = getPageDescription(); const keywords = getPageKeywords(); - + let ext = {}; + try { + ext = { + eids, + firstPartyData, + ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, + pmguid: getPmgUID(), + ssftUid: storage.getDataFromLocalStorage(STORAGE_KEY_FTUID) || undefined, + ssimUid: storage.getDataFromLocalStorage(STORAGE_KEY_IMUID) || undefined, + sspbid: storage.getCookie(COOKIE_KEY_PBUID) || undefined, + tpData, + utm: storage.getCookie(UTM_KEY), + page: { + title: title ? title.slice(0, 150) : undefined, + desc: desc ? desc.slice(0, 300) : undefined, + keywords: keywords ? keywords.slice(0, 100) : undefined, + hLen: getHLen(), + }, + device: { + nbw: getConnectionDownLink(), + } + } + } catch (error) {} try { buildUTMTagData(page); } catch (error) { } @@ -480,28 +314,10 @@ function getParam(validBidRequests, bidderRequest) { ua: navigator.userAgent, language: /en/.test(navigator.language) ? 'en' : navigator.language, }, - ext: { - eids, - firstPartyData, - ssppid: storage.getCookie(COOKIE_KEY_SSPPID) || undefined, - pmguid: getPmgUID(), - tpData, - utm: storage.getCookie(UTM_KEY), - page: { - title: title ? title.slice(0, 100) : undefined, - desc: desc ? desc.slice(0, 300) : undefined, - keywords: keywords ? keywords.slice(0, 100) : undefined, - hLen: topWindow.history?.length || undefined, - }, - device: { - nbw: getConnectionDownLink(), - hc: topWindow.navigator?.hardwareConcurrency || undefined, - dm: topWindow.navigator?.deviceMemory || undefined, - } - }, + ext, user: { buyeruid: storage.getCookie(COOKIE_KEY_MGUID) || undefined, - id: sharedid || pubcid, + id: sharedid, }, tmax: timeout, site: { @@ -562,14 +378,15 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { - if (!globals['token']) return; + const pbToken = globals['token']; + if (!pbToken) return; let payload = getParam(validBidRequests, bidderRequest); - const payloadString = JSON.stringify(payload); + return { method: 'POST', - url: ENDPOINT_URL + globals['token'], + url: `${ENDPOINT_URL}${pbToken}`, data: payloadString, }; }, @@ -693,7 +510,7 @@ export const spec = { const response = event.data; if (!response.optout && response.mguid) { - storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCookieTimeToUTCString()); } }, true); return [ diff --git a/modules/djaxBidAdapter.js b/modules/djaxBidAdapter.js new file mode 100644 index 00000000000..7a9e359f520 --- /dev/null +++ b/modules/djaxBidAdapter.js @@ -0,0 +1,113 @@ +import { registerBidder } from '../src/adapters/bidderFactory.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://revphpe.djaxbidder.com/header_bidding_vast/'; +const RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +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 sendResponseToServer(data) { + ajax(DOMAIN + 'www/admin/plugins/Prebid/tracking/track.php', null, JSON.stringify(data), { + withCredentials: false, + method: 'POST', + crossOrigin: true + }); +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_AD_TYPES, + + isBidRequestValid: function(bid) { + return (typeof bid.params !== 'undefined' && parseInt(utils.getValue(bid.params, 'publisherId')) > 0); + }, + + buildRequests: function(validBidRequests) { + return { + method: 'POST', + url: DOMAIN + 'www/admin/plugins/Prebid/getAd.php', + options: { + withCredentials: false, + crossOrigin: true + }, + data: validBidRequests, + }; + }, + + interpretResponse: function(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.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; + }, + + onBidWon: function(bid) { + let wonBids = []; + wonBids.push(bid); + wonBids[0].function = 'onBidWon'; + sendResponseToServer(wonBids); + }, + + onTimeout: function(details) { + details.unshift({ 'function': 'onTimeout' }); + sendResponseToServer(details); + } +}; + +registerBidder(spec); diff --git a/modules/djaxBidAdapter.md b/modules/djaxBidAdapter.md new file mode 100644 index 00000000000..d36a92de458 --- /dev/null +++ b/modules/djaxBidAdapter.md @@ -0,0 +1,51 @@ +# Overview + +``` +Module Name: Djax Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@djaxtech.com +``` + +# Description + +Module that connects to Djax + +# Test Parameters +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]], // a display size + } + }, + bids: [ + { + bidder: "djax", + params: { + publisherId: '2' // string - required + } + } + ] + } + ]; +``` + var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + playerSize: [[480, 320]], // a display size + } + }, + bids: [ + { + bidder: "djax", + params: { + publisherId: '12' // string - required + } + } + ] + } + ]; \ No newline at end of file diff --git a/modules/docereeAdManagerBidAdapter.js b/modules/docereeAdManagerBidAdapter.js index d3765f5a130..e26045c8f1f 100644 --- a/modules/docereeAdManagerBidAdapter.js +++ b/modules/docereeAdManagerBidAdapter.js @@ -20,12 +20,12 @@ export const spec = { } return true; }, - buildRequests: (validBidRequests) => { + buildRequests: (validBidRequests, bidderRequest) => { const serverRequests = []; const { data } = config.getConfig('docereeadmanager.user') || {}; validBidRequests.forEach(function (validBidRequest) { - const payload = getPayload(validBidRequest, data); + const payload = getPayload(validBidRequest, data, bidderRequest); if (!payload) { return; @@ -70,20 +70,30 @@ export const spec = { }, }; -function getPayload(bid, userData) { +export function getPageUrl() { + let url = ''; + try { + url = window.location.href; + } catch (error) { + } + return url; +} + +export function getPayload(bid, userData, bidderRequest) { if (!userData || !bid) { return false; } const { bidId, params } = bid; - const { placementId } = params; + const { placementId, publisherUrl } = params; const { userid, email, firstname, lastname, - specialization, hcpid, + dob, + specialization, gender, city, state, @@ -94,11 +104,12 @@ function getPayload(bid, userData) { hashedmobile, country, organization, - dob, + platformUid, + mobile } = userData; const data = { - userid: userid || '', + userid: platformUid || userid || '', email: email || '', firstname: firstname || '', lastname: lastname || '', @@ -119,7 +130,22 @@ function getPayload(bid, userData) { organization: organization || '', dob: dob || '', userconsent: 1, + mobile: mobile || '', + pageurl: publisherUrl || getPageUrl() || '' }; + + try { + if (bidderRequest && bidderRequest.gdprConsent) { + const { gdprApplies, consentString } = bidderRequest.gdprConsent; + data['consent'] = { + 'gdpr': gdprApplies ? 1 : 0, + 'gdprstr': consentString || '', + } + } + } catch (error) { + + } + return { data, }; diff --git a/modules/driftpixelBidAdapter.js b/modules/driftpixelBidAdapter.js new file mode 100644 index 00000000000..5dd0d3a5835 --- /dev/null +++ b/modules/driftpixelBidAdapter.js @@ -0,0 +1,18 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'driftpixel'; +const ENDPOINT = 'https://pbjs.driftpixel.live'; + +export const spec = { + code: BIDDER_CODE, + aliases: ['driftpixel'], + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), + interpretResponse, + getUserSyncs +} + +registerBidder(spec); diff --git a/modules/driftpixelBidAdapter.md b/modules/driftpixelBidAdapter.md new file mode 100644 index 00000000000..277c363bbf0 --- /dev/null +++ b/modules/driftpixelBidAdapter.md @@ -0,0 +1,54 @@ +# Overview + +``` +Module Name: Driftpixel Bidder Adapter +Module Type: Driftpixel Bidder Adapter +Maintainer: developer@driftpixel.ai +``` + +# Description + +Module that connects to driftpixel.com demand sources + +# Test Parameters +``` +var adUnits = [ + { + code: 'test-banner', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + bids: [ + { + bidder: 'driftpixel', + params: { + env: 'driftpixel', + pid: '40', + ext: {} + } + } + ] + }, + { + code: 'test-video', + sizes: [ [ 640, 480 ] ], + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } + }, + bids: [{ + bidder: 'driftpixel', + params: { + env: 'driftpixel', + pid: '40', + ext: {} + } + }] + } +]; +``` diff --git a/modules/dspxBidAdapter.js b/modules/dspxBidAdapter.js index ea47c64094d..acb5fb64d81 100644 --- a/modules/dspxBidAdapter.js +++ b/modules/dspxBidAdapter.js @@ -1,18 +1,31 @@ -import {deepAccess, getBidIdParameter, isFn, logError, logMessage, logWarn} from '../src/utils.js'; +import {deepAccess, logMessage, getBidIdParameter, logError, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; import {includes} from '../src/polyfill.js'; +import { + fillUsersIds, + handleSyncUrls, + objectToQueryString, + isBannerRequest, + getVideoContext, + convertMediaInfoForRequest, + getMediaTypesInfo, + getBidFloor, + siteContentToString, + assignDefinedValues, + extractUserSegments, + interpretResponse +} from '../libraries/dspxUtils/bidderUtils.js'; +import {Renderer} from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest */ - const BIDDER_CODE = 'dspx'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; const GVLID = 602; -const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'linearity', 'skip', 'skipmin', +const VIDEO_ORTB_PARAMS = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype', 'ext']; @@ -63,24 +76,18 @@ export const spec = { rnd: rnd, ref: referrer, bid_id: bidId, - pbver: '$prebid.version$' + pbver: '$prebid.version$', }; + payload.pfilter = {}; if (params.pfilter !== undefined) { payload.pfilter = params.pfilter; } if (bidderRequest && bidderRequest.gdprConsent) { - if (payload.pfilter !== undefined) { - if (!payload.pfilter.gdpr_consent) { - 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 (!payload.pfilter.gdpr_consent) { + payload.pfilter.gdpr_consent = bidderRequest.gdprConsent.consentString; + payload.pfilter.gdpr = bidderRequest.gdprConsent.gdprApplies; } } @@ -94,48 +101,10 @@ export const spec = { payload.prebidDevMode = 1; } - // fill userId params - if (bidRequest.userId) { - if (bidRequest.userId.netId) { - payload.did_netid = bidRequest.userId.netId; - } - if (bidRequest.userId.id5id) { - payload.did_id5 = bidRequest.userId.id5id.uid || '0'; - if (bidRequest.userId.id5id.ext.linkType !== undefined) { - payload.did_id5_linktype = bidRequest.userId.id5id.ext.linkType; - } - } - let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); - if (uId2) { - payload.did_uid2 = uId2; - } - let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); - if (sharedId) { - payload.did_sharedid = sharedId; - } - let pubcId = deepAccess(bidRequest, 'userId.pubcid'); - if (pubcId) { - payload.did_pubcid = pubcId; - } - let crumbsPubcid = deepAccess(bidRequest, 'crumbs.pubcid'); - if (crumbsPubcid) { - payload.did_cpubcid = crumbsPubcid; - } - } - - if (bidRequest.schain) { - payload.schain = bidRequest.schain; - } - - if (payload.pfilter === undefined || !payload.pfilter.floorprice) { + if (!payload.pfilter.floorprice) { let bidFloor = getBidFloor(bidRequest); if (bidFloor > 0) { - if (payload.pfilter !== undefined) { - payload.pfilter.floorprice = bidFloor; - } else { - payload.pfilter = { 'floorprice': bidFloor }; - } - // payload.bidFloor = bidFloor; + payload.pfilter.floorprice = bidFloor; } } @@ -146,6 +115,7 @@ export const spec = { payload.pbcode = pbcode; } + // media types payload.media_types = convertMediaInfoForRequest(mediaTypesInfo); if (mediaTypesInfo[VIDEO] !== undefined) { payload.vctx = getVideoContext(bidRequest); @@ -159,6 +129,33 @@ export const spec = { .forEach(key => payload.vpl[key] = videoParams[key]); } + // iab content + let content = deepAccess(bidderRequest, 'ortb2.site.content'); + if (content) { + let stringContent = siteContentToString(content); + if (stringContent) { + payload.pfilter.iab_content = stringContent; + } + } + + // Google Topics + const segments = extractUserSegments(bidderRequest); + if (segments) { + assignDefinedValues(payload, { + segtx: segments.segtax, + segcl: segments.segclass, + segs: segments.segments + }); + } + + // schain + if (bidRequest.schain) { + payload.schain = bidRequest.schain; + } + + // fill userId params + fillUsersIds(bidRequest, payload); + return { method: 'GET', url: endpoint, @@ -169,282 +166,20 @@ export const spec = { interpretResponse: function(serverResponse, bidRequest) { logMessage('DSPx: serverResponse', serverResponse); logMessage('DSPx: bidRequest', 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, - type: response.type, - ttl: 60, - meta: { - advertiserDomains: response.adomain || [] - } - }; - - if (response.vastUrl) { - bidResponse.vastUrl = response.vastUrl; - bidResponse.mediaType = 'video'; - } - if (response.vastXml) { - bidResponse.vastXml = response.vastXml; - bidResponse.mediaType = 'video'; - } - if (response.renderer) { - bidResponse.renderer = newRenderer(bidRequest, response); - } - - if (response.videoCacheKey) { - bidResponse.videoCacheKey = response.videoCacheKey; - } - - if (response.adTag) { - bidResponse.ad = response.adTag; - } - - if (response.bid_appendix) { - Object.keys(response.bid_appendix).forEach(fieldName => { - bidResponse[fieldName] = response.bid_appendix[fieldName]; - }); - } - - bidResponses.push(bidResponse); - } - return bidResponses; + return interpretResponse(serverResponse, bidRequest, (bidRequest, response) => newRenderer(bidRequest, response)); }, 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; + return handleSyncUrls(syncOptions, serverResponses, gdprConsent); } } -function appendToUrl(url, what) { - if (!what) { - return url; - } - return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; -} - -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) + '=' + encodeURIComponent(v)); - } - } - 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' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); -} - -/** - * Check if it's a video bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a video bid - */ -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); -} - -/** - * Get video sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} - */ -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -/** - * Get video context - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} - */ -function getVideoContext(bid) { - return deepAccess(bid, 'mediaTypes.video.context') || 'unknown'; -} - -/** - * Get banner sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -/** - * Parse size - * @param size - * @returns {object} sizeObj - */ -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]) -} - -/** - * Get MediaInfo object for server request - * - * @param mediaTypesInfo - * @returns {*} - */ -function convertMediaInfoForRequest(mediaTypesInfo) { - let requestData = {}; - Object.keys(mediaTypesInfo).forEach(mediaType => { - requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { - return size.width + 'x' + size.height; - }).join(','); - }); - return requestData; -} - -/** - * Get media types info - * - * @param bid - */ -function getMediaTypesInfo(bid) { - let mediaTypesInfo = {}; - - if (bid.mediaTypes) { - Object.keys(bid.mediaTypes).forEach(mediaType => { - if (mediaType === BANNER) { - mediaTypesInfo[mediaType] = getBannerSizes(bid); - } - if (mediaType === VIDEO) { - mediaTypesInfo[mediaType] = getVideoSizes(bid); - } - }); - } else { - mediaTypesInfo[BANNER] = getBannerSizes(bid); - } - return mediaTypesInfo; -} - -/** - * Get Bid Floor - * @param bid - * @returns {number|*} - */ -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'EUR', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -/** - * Create a new renderer - * - * @param bidRequest - * @param response - * @returns {Renderer} - */ -function newRenderer(bidRequest, response) { - logMessage('DSPx: newRenderer', bidRequest, response); - const renderer = Renderer.install({ - id: response.renderer.id || response.bid_id, - url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, - config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), - loaded: false - }); - - try { - renderer.setRender(outstreamRender); - } catch (err) { - logWarn('Prebid Error calling setRender on renderer', err); - } - return renderer; -} - /** * Outstream Render Function * * @param bid */ function outstreamRender(bid) { - logMessage('DSPx: outstreamRender bid:', bid); + logMessage('[DSPx][outstreamRender] bid:', bid); const embedCode = createOutstreamEmbedCode(bid); try { const inIframe = getBidIdParameter('iframe', bid.renderer.config); @@ -455,7 +190,7 @@ function outstreamRender(bid) { if (typeof window.dspxRender === 'function') { window.dspxRender(bid); } else { - logError('[dspx][renderer] Error: dspxRender function is not found'); + logError('[DSPx][outstreamRender] Error: dspxRender function is not found'); } return; } @@ -466,13 +201,13 @@ function outstreamRender(bid) { if (typeof window.dspxRender === 'function') { window.dspxRender(bid); } else { - logError('[dspx][renderer] Error: dspxRender function is not found'); + logError('[DSPx][outstreamRender] Error: dspxRender function is not found'); } } else if (slot) { - logError('[dspx][renderer] Error: slot not found'); + logError('[DSPx][outstreamRender] Error: slot not found'); } } catch (err) { - logError('[dspx][renderer] Error:' + err.message) + logError('[DSPx][outstreamRender] Error:' + err.message) } } @@ -508,4 +243,28 @@ function createOutstreamEmbedCode(bid) { return fragment; } +/** + * Create a new renderer + * + * @param bidRequest + * @param response + * @returns {Renderer} + */ +function newRenderer(bidRequest, response) { + logMessage('[DSPx] newRenderer', bidRequest, response); + const renderer = Renderer.install({ + id: response.renderer.id || response.bid_id, + url: (bidRequest.params && bidRequest.params.rendererUrl) || response.renderer.url, + config: response.renderer.options || deepAccess(bidRequest, 'renderer.options'), + loaded: false + }); + + try { + renderer.setRender(outstreamRender); + } catch (err) { + logWarn('[DSPx]Prebid Error calling setRender on renderer', err); + } + return renderer; +} + registerBidder(spec); diff --git a/modules/dynamicAdBoostRtdProvider.js b/modules/dynamicAdBoostRtdProvider.js index 697bd7340d3..a68567b1ca3 100644 --- a/modules/dynamicAdBoostRtdProvider.js +++ b/modules/dynamicAdBoostRtdProvider.js @@ -8,6 +8,7 @@ import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { deepAccess, deepSetValue, isEmptyStr } from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -72,7 +73,7 @@ function loadLmScript(keyId) { let viewableAdUnits = Object.keys(dynamicAdBoostAdUnits); let viewableAdUnitsCSV = viewableAdUnits.join(','); const scriptUrl = `${SCRIPT_URL}/${keyId}.js?viewableAdUnits=${viewableAdUnitsCSV}`; - loadExternalScript(scriptUrl, MODULE_NAME); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, MODULE_NAME); observer.disconnect(); } diff --git a/modules/e_volutionBidAdapter.js b/modules/e_volutionBidAdapter.js index 2ce6cda16d1..e87e39599a0 100644 --- a/modules/e_volutionBidAdapter.js +++ b/modules/e_volutionBidAdapter.js @@ -1,183 +1,19 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'e_volution'; const GVLID = 957; 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; - -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; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - noSync: NO_SYNC, - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - let placements = []; - let request = { - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language.split('-')[0] : '', - 'secure': 1, - 'host': location.host, - 'page': location.pathname, - 'placements': placements - }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } - const len = validBidRequests.length; - - for (let i = 0; i < len; i++) { - let bid = validBidRequests[i]; - - const placement = { - placementId: bid.params.placementId, - bidId: bid.bidId, - bidfloor: getBidFloor(bid), - eids: [] - }; - - if (bid.userId) { - getUserId(placement.eids, bid.userId.id5id, 'id5-sync.com'); - } - - if (bid.mediaTypes && bid.mediaTypes[BANNER] && bid.mediaTypes[BANNER].sizes) { - placement.traffic = BANNER; - placement.sizes = bid.mediaTypes[BANNER].sizes; - } else if (bid.mediaTypes && bid.mediaTypes[VIDEO] && bid.mediaTypes[VIDEO].playerSize) { - placement.traffic = VIDEO; - 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.traffic = NATIVE; - placement.native = bid.mediaTypes[NATIVE]; - } - - if (bid.schain) { - placements.schain = bid.schain; - } - - 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) => { - if (NO_SYNC) { - return false - } else { - return [{ - type: 'image', - url: URL_SYNC - }]; - } - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/ebdrBidAdapter.js b/modules/ebdrBidAdapter.js deleted file mode 100644 index e830f8a94f7..00000000000 --- a/modules/ebdrBidAdapter.js +++ /dev/null @@ -1,156 +0,0 @@ -import {getBidIdParameter, logInfo} from '../src/utils.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -const BIDDER_CODE = 'ebdr'; -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [ BANNER, VIDEO ], - isBidRequestValid: function(bid) { - return !!(bid && bid.params && bid.params.zoneid); - }, - buildRequests: function(bids) { - const rtbServerDomain = 'dsp.bnmla.com'; - let domain = window.location.host; - let page = window.location.pathname + location.search + location.hash; - let ebdrImps = []; - const ebdrReq = {}; - let ebdrParams = {}; - let zoneid = ''; - let requestId = ''; - bids.forEach(bid => { - logInfo('Log bid', bid); - let bidFloor = getBidIdParameter('bidfloor', bid.params); - let whArr = getWidthAndHeight(bid); - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video) ? VIDEO : BANNER; - zoneid = getBidIdParameter('zoneid', bid.params); - requestId = bid.bidderRequestId; - ebdrImps.push({ - id: bid.bidId, - [_mediaTypes]: { - w: whArr[0], - h: whArr[1] - }, - bidfloor: bidFloor - }); - ebdrReq[bid.bidId] = {mediaTypes: _mediaTypes, - w: whArr[0], - h: whArr[1] - }; - // TODO: fix lat and long to only come from request - ebdrParams['latitude'] = '0'; - ebdrParams['longitude'] = '0'; - ebdrParams['ifa'] = (getBidIdParameter('IDFA', bid.params).length > getBidIdParameter('ADID', bid.params).length) ? getBidIdParameter('IDFA', bid.params) : getBidIdParameter('ADID', bid.params); - }); - let ebdrBidReq = { - id: requestId, - imp: ebdrImps, - site: { - domain: domain, - page: page - }, - device: { - geo: { - lat: ebdrParams.latitude, - log: ebdrParams.longitude - }, - ifa: ebdrParams.ifa - } - }; - return { - method: 'GET', - url: 'https://' + rtbServerDomain + '/hb?' + '&zoneid=' + zoneid + '&br=' + encodeURIComponent(JSON.stringify(ebdrBidReq)), - bids: ebdrReq - }; - }, - interpretResponse: function(serverResponse, bidRequest) { - logInfo('Log serverResponse', serverResponse); - logInfo('Log bidRequest', bidRequest); - let ebdrResponseImps = []; - const ebdrResponseObj = serverResponse.body; - if (!ebdrResponseObj || !ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { - return []; - } - ebdrResponseObj.seatbid[0].bid.forEach(ebdrBid => { - let responseCPM; - responseCPM = parseFloat(ebdrBid.price); - let adm; - let type; - let _mediaTypes; - let vastURL; - if (bidRequest.bids[ebdrBid.id].mediaTypes == BANNER) { - adm = decodeURIComponent(ebdrBid.adm) - type = 'ad'; - _mediaTypes = BANNER; - } else { - adm = ebdrBid.adm - type = 'vastXml' - _mediaTypes = VIDEO; - if (ebdrBid.nurl) { - vastURL = ebdrBid.nurl; - } - } - let response = { - requestId: ebdrBid.id, - [type]: adm, - mediaType: _mediaTypes, - creativeId: ebdrBid.crid, - cpm: responseCPM, - width: ebdrBid.w, - height: ebdrBid.h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - meta: { - advertiserDomains: ebdrBid.adomain || [] - } - }; - if (vastURL) { - response.vastUrl = vastURL; - } - ebdrResponseImps.push(response); - }); - return ebdrResponseImps; - }, - getUserSyncs: function(syncOptions, serverResponses) { - const syncs = [] - if (syncOptions.pixelEnabled) { - const ebdrResponseObj = serverResponses.body; - if (!ebdrResponseObj || !ebdrResponseObj.seatbid || ebdrResponseObj.seatbid.length === 0 || !ebdrResponseObj.seatbid[0].bid || ebdrResponseObj.seatbid[0].bid.length === 0) { - return []; - } - ebdrResponseObj.seatbid[0].bid.forEach(ebdrBid => { - if (ebdrBid.iurl && ebdrBid.iurl.length > 0) { - syncs.push({ - type: 'image', - url: ebdrBid.iurl - }); - } - }); - } - return syncs; - } -} -function getWidthAndHeight(bid) { - let adW = null; - let adH = null; - // Handing old bidder only has size object - if (bid.sizes && bid.sizes.length) { - let sizeArrayLength = bid.sizes.length; - if (sizeArrayLength === 2 && typeof bid.sizes[0] === 'number' && typeof bid.sizes[1] === 'number') { - adW = bid.sizes[0]; - adH = bid.sizes[1]; - } - } - let _mediaTypes = bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER; - if (bid.mediaTypes && bid.mediaTypes[_mediaTypes]) { - if (_mediaTypes == BANNER && bid.mediaTypes[_mediaTypes].sizes && bid.mediaTypes[_mediaTypes].sizes[0] && bid.mediaTypes[_mediaTypes].sizes[0].length === 2) { - adW = bid.mediaTypes[_mediaTypes].sizes[0][0]; - adH = bid.mediaTypes[_mediaTypes].sizes[0][1]; - } else if (_mediaTypes == VIDEO && bid.mediaTypes[_mediaTypes].playerSize && bid.mediaTypes[_mediaTypes].playerSize.length === 2) { - adW = bid.mediaTypes[_mediaTypes].playerSize[0]; - adH = bid.mediaTypes[_mediaTypes].playerSize[1]; - } - } - return [adW, adH]; -} -registerBidder(spec); diff --git a/modules/ebdrBidAdapter.md b/modules/ebdrBidAdapter.md deleted file mode 100644 index 64483797b25..00000000000 --- a/modules/ebdrBidAdapter.md +++ /dev/null @@ -1,53 +0,0 @@ -# Overview - -``` -Module Name: EngageBDR Bid Adapter -Module Type: Bidder Adapter -Maintainer: tech@engagebdr.com -``` - -# Description - -Adapter that connects to EngageBDR's demand sources. - -# Test Parameters -``` - var adUnits = [{ - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300,600]], - } - }, - bids: [{ - bidder: 'ebdr', - params: { - zoneid: '99999', - bidfloor: '1.00', - IDFA:'xxx-xxx', - ADID:'xxx-xxx', - latitude:'34.089811', - longitude:'-118.392805' - } - }] - },{ - code: 'test-video', - mediaTypes: { - video: { - context: 'instream', - playerSize: [300, 250] - } - }, - bids: [{ - bidder: 'ebdr', - params: { - zoneid: '99998', - bidfloor: '1.00', - IDFA:'xxx-xxx', - ADID:'xxx-xxx', - latitude:'34.089811', - longitude:'-118.392805' - } - }] - }]; -``` diff --git a/modules/eclickadsBidAdapter.js b/modules/eclickadsBidAdapter.js new file mode 100644 index 00000000000..27e3926afe3 --- /dev/null +++ b/modules/eclickadsBidAdapter.js @@ -0,0 +1,80 @@ +import { NATIVE } from '../src/mediaTypes.js'; +import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; + +// ***** ECLICKADS ADAPTER ***** +export const BIDDER_CODE = 'eclickads'; +const DEFAULT_CURRENCY = ['USD']; +const DEFAULT_TTL = 1000; +export const ENDPOINT = 'https://g.eclick.vn/rtb_hb_request?fosp_uid='; +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [NATIVE], + isBidRequestValid: (bid) => { + return !!bid && !!bid.params && !!bid.bidder && !!bid.params.zid; + }, + buildRequests: (validBidRequests = [], bidderRequest) => { + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + + const ortb2ConfigFPD = bidderRequest.ortb2.site.ext?.data || {}; + const ortb2Device = bidderRequest.ortb2.device; + const ortb2Site = bidderRequest.ortb2.site; + + const isMobile = getDevice(); + const imp = []; + const fENDPOINT = ENDPOINT + (ortb2ConfigFPD.fosp_uid || ''); + const request = { + deviceWidth: ortb2Device.w, + deviceHeight: ortb2Device.h, + ua: ortb2Device.ua, + language: ortb2Device.language, + device: isMobile ? 'mobile' : 'desktop', + host: ortb2Site.domain, + page: ortb2Site.page, + imp, + myvne_id: ortb2ConfigFPD.myvne_id || '', + orig_aid: ortb2ConfigFPD.orig_aid, + fosp_aid: ortb2ConfigFPD.fosp_aid, + fosp_uid: ortb2ConfigFPD.fosp_uid, + id: ortb2ConfigFPD.id, + }; + + validBidRequests.map((bid) => { + imp.push({ + requestId: bid.bidId, + adUnitCode: bid.adUnitCode, + zid: bid.params.zid, + }); + }); + + return { + method: 'POST', + url: fENDPOINT, + data: request, + }; + }, + interpretResponse: (serverResponse) => { + const seatbid = serverResponse.body?.seatbid || []; + return seatbid.reduce((bids, bid) => { + return [ + ...bids, + { + id: bid.id, + impid: bid.impid, + adUnitCode: bid.adUnitCode, + cpm: bid.cpm, + ttl: bid.ttl || DEFAULT_TTL, + requestId: bid.requestId, + creativeId: bid.creativeId, + netRevenue: bid.netRevenue, + currency: bid.currency || DEFAULT_CURRENCY, + adserverTargeting: { + hb_ad_eclickads: bid.ad, + }, + }, + ]; + }, []); + }, +}; +registerBidder(spec); diff --git a/modules/eclickadsBidAdapter.md b/modules/eclickadsBidAdapter.md new file mode 100644 index 00000000000..39ba19d5249 --- /dev/null +++ b/modules/eclickadsBidAdapter.md @@ -0,0 +1,55 @@ +# Overview + +Module Name: EClickAds Bid Adapter +Type: Bidder Adapter +Maintainer: vietlv14@fpt.com + +# Description + +This module connects to EClickAds exchange for bidding NATIVE ADS via prebid.js + +# Test Parameters + +``` +var adUnits = [{ + code: 'test-div', + mediaTypes: { + native: { + title: { + required: true, + len: 50 + }, + body: { + required: true, + len: 350 + }, + url: { + required: true + }, + image: { + required: true, + sizes : [300, 175] + }, + sponsoredBy: { + required: true + } + } + }, + bids: [{ + bidder: 'eclickads', + params: { + zid:"7096" + } + }] +}]; +``` + +# Notes: + +- EClickAdsBidAdapter need serveral params inside bidder config as following + - user.myvne_id + - site.orig_aid + - site.fosp_aid + - site.id + - site.orig_aid +- EClickAdsBidAdapter will set bid.adserverTargeting.hb_ad_eclickads targeting key while submitting bid to AdServer diff --git a/modules/edge226BidAdapter.js b/modules/edge226BidAdapter.js index 6d1e2466abe..ae235f02f64 100644 --- a/modules/edge226BidAdapter.js +++ b/modules/edge226BidAdapter.js @@ -1,188 +1,17 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'edge226'; const AD_URL = 'https://ssp.dauup.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, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/ehealthcaresolutionsBidAdapter.js b/modules/ehealthcaresolutionsBidAdapter.js new file mode 100644 index 00000000000..9df4c38e4f2 --- /dev/null +++ b/modules/ehealthcaresolutionsBidAdapter.js @@ -0,0 +1,41 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, + getNativeResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const ENDPOINT_URL = 'https://rtb.ehealthcaresolutions.com/hb'; +// Export const spec +export const spec = { + code: 'ehealthcaresolutions', + supportedMediaTypes: [BANNER, NATIVE], + // Determines whether or not the given bid request is valid + isBidRequestValid: (bParam) => { + return !!(bParam.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: (bidRequests, serverRequest) => { + // Get Requests based on media types + return getBannerRequest(bidRequests, serverRequest, ENDPOINT_URL); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: (bResponse, bRequest) => { + let Response = {}; + const mediaType = JSON.parse(bRequest.data)[0].MediaType; + if (mediaType == BANNER) { + Response = getBannerResponse(bResponse, BANNER); + } else if (mediaType == NATIVE) { + Response = getNativeResponse(bResponse, bRequest, NATIVE); + } + return Response; + } +} + +registerBidder(spec); diff --git a/modules/ehealthcaresolutionsBidAdapter.md b/modules/ehealthcaresolutionsBidAdapter.md new file mode 100644 index 00000000000..fdf859404d2 --- /dev/null +++ b/modules/ehealthcaresolutionsBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: eHealthcareSolutions Bidder Adapter +Module Type: Bidder Adapter +Maintainer: info@ehsmail.com +``` + +# Description + +eHealthcareSolutions currently supports the BANNER and NATIVE type ads through prebid js + +Module that connects to eHealthcareSolutions's demand sources. + +# Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'ehealthcaresolutions', + params: { + placement_id: 111519, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + }, + { + code: 'native-ad-container', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + bids: [ + { + bidder: 'eHealthcareSolutions', + params: { + placement_id: 111519, // Required parameter + bid_floor: 1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/modules/eightPodAnalyticsAdapter.js b/modules/eightPodAnalyticsAdapter.js index 64ff845505d..e91f9412ef5 100644 --- a/modules/eightPodAnalyticsAdapter.js +++ b/modules/eightPodAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {logError, logInfo} from '../src/utils.js'; +import {logError, logInfo, logMessage} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS } from '../src/constants.js'; @@ -14,7 +14,6 @@ const MODULE = `${MODULE_NAME}AnalyticProvider`; * Custom tracking server that gets internal events from EightPod's ad unit */ const trackerUrl = 'https://demo.8pod.com/tracker/track'; - export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) const { @@ -22,12 +21,12 @@ const { } = EVENTS; export let queue = []; -export let context; +let context = {}; /** * Create eightPod Analytic adapter */ -let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { +let eightPodAnalytics = Object.assign(adapter({url: trackerUrl, analyticsType}), { /** * Execute on bid won - setup basic settings, save context about EightPod's bid. We will send it with our events later */ @@ -35,43 +34,49 @@ let eightPodAnalytics = Object.assign(adapter({ analyticsType }), { switch (eventType) { case BID_WON: if (args.bidder === 'eightPod') { + context[args.adUnitCode] = makeContext(args); + eightPodAnalytics.setupPage(args); - context = makeContext(args); break; } } }, /** - * Execute on bid won - subscribe on events from adUnit + * Execute on bid won upload events from local storage */ setupPage() { - eightPodAnalytics.eventSubscribe(); - queue = getEventFromLocalStorage(); + queue = this.getEventFromLocalStorage(); }, + /** * Subscribe on internal ad unit tracking events */ eventSubscribe() { window.addEventListener('message', async (event) => { - const data = event?.data; + const data = event.data; - if (!data?.detail?.name) { - return; - } + const frameElement = event.source?.frameElement; + const parentElement = frameElement?.parentElement; + const adUnitCode = parentElement?.id; - trackEvent(data); + trackEvent(data, adUnitCode); }); - // Send queue of event every 10 seconds - setInterval(sendEvents, 10_000); + if (!this._interval) { + this._interval = setInterval(sendEvents, 10_000); + } }, resetQueue() { queue = []; }, getContext() { return context; - } + }, + resetContext() { + context = {}; + }, + getEventFromLocalStorage, }); /** @@ -81,8 +86,8 @@ function makeContext(args) { const params = args?.params?.[0]; return { bidId: args.seatBidId, - variantId: args.creativeId || 'variantId', - campaignId: 'campaignId', + variantId: args.creativeId || '', + campaignId: args.cid || '', publisherId: params.publisherId, placementId: params.placementId, }; @@ -91,12 +96,13 @@ function makeContext(args) { /** * Create event, add context and push it to queue */ -export function trackEvent(event) { +export function trackEvent(event, adUnitCode) { if (!event.detail) { return; } + const fullEvent = { - context: eightPodAnalytics.getContext(), + context: eightPodAnalytics.getContext()[adUnitCode], eventType: event.detail.type, eventClass: 'adunit', timestamp: new Date().getTime(), @@ -104,6 +110,7 @@ export function trackEvent(event) { payload: event.detail.payload }; + logMessage(fullEvent); addEvent(fullEvent); } @@ -155,7 +162,7 @@ function sendEvents() { * Send event to our custom tracking server */ function sendEventsApi(eventList, callbacks) { - ajax(trackerUrl, callbacks, JSON.stringify(eventList)); + ajax(trackerUrl, callbacks, JSON.stringify(eventList), {keepalive: true}); } /** @@ -170,11 +177,23 @@ eightPodAnalytics.originEnableAnalytics = eightPodAnalytics.enableAnalytics; eightPodAnalytics.eventsStorage = []; // override enableAnalytics so we can get access to the config passed in from the page +// Subscribe on events from adUnit eightPodAnalytics.enableAnalytics = function (config) { eightPodAnalytics.originEnableAnalytics(config); logInfo(MODULE, 'init', config); + eightPodAnalytics.eventSubscribe(); }; +eightPodAnalytics.disableAnalytics = ((orig) => { + return function () { + if (this._interval) { + clearInterval(this._interval); + this._interval = null; + } + return orig.apply(this, arguments); + } +})(eightPodAnalytics.disableAnalytics) + /** * Register Analytics Adapter */ diff --git a/modules/eightPodBidAdapter.js b/modules/eightPodBidAdapter.js index 02f0954af07..536bc4b4036 100644 --- a/modules/eightPodBidAdapter.js +++ b/modules/eightPodBidAdapter.js @@ -4,7 +4,7 @@ import { BANNER } from '../src/mediaTypes.js' import * as utils from '../src/utils.js' export const BIDDER_CODE = 'eightPod' -const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid?trace=true'; +const url = 'https://demo.8pod.com/bidder/rtb/eightpod_exchange/bid'; export const spec = { code: BIDDER_CODE, @@ -49,8 +49,9 @@ function isBidRequestValid(bidRequest) { function buildRequests(bids, bidderRequest) { let bannerBids = bids.filter((bid) => isBannerBid(bid)) let requests = bannerBids.length - ? [createRequest(bannerBids, bidderRequest, BANNER)] + ? createRequest(bannerBids, bidderRequest, BANNER) : [] + return requests } @@ -61,8 +62,10 @@ function bidResponse(buildBidResponse, bid, context) { bidResponse.height = context?.imp?.banner?.format?.[0].h; bidResponse.width = context?.imp?.banner?.format?.[0].w; + bidResponse.cid = bid.cid; bidResponse.burl = replacePriceInUrl(bid.burl, bidResponse.originalCpm || bidResponse.cpm); + return bidResponse; } @@ -119,64 +122,75 @@ export function getPageKeywords(win = window) { } function createRequest(bidRequests, bidderRequest, mediaType) { - const data = converter.toORTB({ - bidRequests, - bidderRequest, - context: { mediaType }, - }); - - data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; - data.at = 1; - - const params = getBidderParams(bidRequests); - - data.device = { - ...data.device, - model: parseUserAgent().device, - os: parseUserAgent().platform, - osv: parseUserAgent().version, - geo: { - country: params.country || 'GRB' - }, - language: params.language || data.device.language, - } - data.site = { - ...data.site, - keywords: getPageKeywords(window), - } - data.imp = [ - { - ...data.imp?.[0], - pmp: params.dealId - ? { - ...data.pmp, - deals: [ - { - bidfloor: 0.5, - at: 2, - id: params.dealId, - }, - ], - private_auction: 1, - } - : data.pmp, + const requests = bidRequests.map((bidRequest) => { + const data = converter.toORTB({ + bidRequests: [bidRequest], + bidderRequest, + context: { mediaType }, + }); + + data.adSlotPositionOnScreen = 'ABOVE_THE_FOLD'; + data.at = 1; + + const userId = + utils.deepAccess(bidRequest, 'userId.unifiedId.id') || + utils.deepAccess(bidRequest, 'userId.id5id.uid') || + utils.deepAccess(bidRequest, 'userId.idl_env'); + + const params = getBidderParams(bidRequest); + data.device = { + ...data.device, + devicetype: 4, + geo: { + country: params.country || 'GRB' + }, + language: params.language || data.device.language, + } + data.site = { + ...data.site, + keywords: getPageKeywords(window), + publisher: { + id: params.publisherId + } + } + data.imp = [ + { + ...data.imp?.[0], + secure: 1, + pmp: params.dealId + ? { + ...data.pmp, + deals: [ + { + id: params.dealId, + }, + ], + private_auction: 1, + } + : data.pmp, + } + ] + data.adSlotPlacementId = params.placementId; + + if (userId) { + data.user = { + id: userId + } } - ] - data.adSlotPlacementId = params.placementId; - const req = { - method: 'POST', - url, - data - } - return req -} + const req = { + method: 'POST', + url: url && params.trace ? url + '?trace=true' : url, + options: { withCredentials: false }, + data + } + return req + }) -function getBidderParams(bidRequests) { - const bid = bidRequests.find(bid => { - return bid.bidder === BIDDER_CODE - }); + return requests; +} +function getBidderParams(bid) { return bid?.params ? bid.params : undefined; } @@ -189,5 +203,47 @@ function isBannerBid(bid) { } function interpretResponse(resp, req) { - return converter.fromORTB({ request: req.data, response: resp.body }) + const impressionId = resp.body.seatbid[0].bid[0].impid; + const bidResponses = converter.fromORTB({ request: req.data, response: resp.body }); + const ad = bidResponses[0].ad; + const trackingTag = ` + + + + + ` + + bidResponses[0].ad = ad.replace('', trackingTag + ''); + return bidResponses; } diff --git a/modules/emtvBidAdapter.js b/modules/emtvBidAdapter.js index 7a2fdae8adf..822fa683d9e 100644 --- a/modules/emtvBidAdapter.js +++ b/modules/emtvBidAdapter.js @@ -1,211 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'emtv'; const AD_URL = 'https://us-east-ep.engagemedia.tv/pbjs'; const SYNC_URL = 'https://cs.engagemedia.tv'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: config.getConfig('bidderTimeout') - }; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - const advertiserDomains = resItem.adomain && resItem.adomain.length ? resItem.adomain : []; - resItem.meta = { ...resItem.meta, advertiserDomains }; - - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { - let syncType = syncOptions.iframeEnabled ? 'iframe' : 'image'; - let syncUrl = SYNC_URL + `/${syncType}?pbjs=1`; - if (gdprConsent && gdprConsent.consentString) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - syncUrl += `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - syncUrl += `&gdpr=0&gdpr_consent=${gdprConsent.consentString}`; - } - } - if (uspConsent && uspConsent.consentString) { - syncUrl += `&ccpa_consent=${uspConsent.consentString}`; - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/enrichmentFpdModule.js b/modules/enrichmentFpdModule.js deleted file mode 100644 index 59d5d326109..00000000000 --- a/modules/enrichmentFpdModule.js +++ /dev/null @@ -1,2 +0,0 @@ -// Logic from this module was moved into core since approx. 7.27 -// TODO: remove this in v8 diff --git a/modules/eplanningAnalyticsAdapter.js b/modules/eplanningAnalyticsAdapter.js deleted file mode 100644 index 45a0be54715..00000000000 --- a/modules/eplanningAnalyticsAdapter.js +++ /dev/null @@ -1,130 +0,0 @@ -import { logError } from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; - -const analyticsType = 'endpoint'; -const EPL_HOST = 'https://ads.us.e-planning.net/hba/1/'; - -function auctionEndHandler(args) { - return {auctionId: args.auctionId}; -} - -function auctionInitHandler(args) { - return { - auctionId: args.auctionId, - time: args.timestamp - }; -} - -function bidRequestedHandler(args) { - return { - auctionId: args.auctionId, - time: args.start, - bidder: args.bidderCode, - bids: args.bids.map(function(bid) { - return { - time: bid.startTime, - bidder: bid.bidder, - placementCode: bid.placementCode, - auctionId: bid.auctionId, - sizes: bid.sizes - }; - }), - }; -} - -function bidResponseHandler(args) { - return { - bidder: args.bidder, - size: args.size, - auctionId: args.auctionId, - cpm: args.cpm, - time: args.responseTimestamp, - }; -} - -function bidWonHandler(args) { - return { - auctionId: args.auctionId, - size: args.width + 'x' + args.height, - }; -} - -function bidTimeoutHandler(args) { - return args.map(function(bid) { - return { - bidder: bid.bidder, - auctionId: bid.auctionId - }; - }) -} - -function callHandler(evtype, args) { - let handler = null; - - if (evtype === EVENTS.AUCTION_INIT) { - handler = auctionInitHandler; - eplAnalyticsAdapter.context.events = []; - } else if (evtype === EVENTS.AUCTION_END) { - handler = auctionEndHandler; - } else if (evtype === EVENTS.BID_REQUESTED) { - handler = bidRequestedHandler; - } else if (evtype === EVENTS.BID_RESPONSE) { - handler = bidResponseHandler - } else if (evtype === EVENTS.BID_TIMEOUT) { - handler = bidTimeoutHandler; - } else if (evtype === EVENTS.BID_WON) { - handler = bidWonHandler; - } - - if (handler) { - eplAnalyticsAdapter.context.events.push({ec: evtype, p: handler(args)}); - } -} - -var eplAnalyticsAdapter = Object.assign(adapter( - { - EPL_HOST, - analyticsType - }), -{ - track({eventType, args}) { - if (typeof args !== 'undefined') { - callHandler(eventType, args); - } - - if (eventType === EVENTS.AUCTION_END) { - try { - let strjson = JSON.stringify(eplAnalyticsAdapter.context.events); - ajax(eplAnalyticsAdapter.context.host + eplAnalyticsAdapter.context.ci + '?d=' + encodeURIComponent(strjson)); - } catch (err) {} - } - } -} -); - -eplAnalyticsAdapter.originEnableAnalytics = eplAnalyticsAdapter.enableAnalytics; - -eplAnalyticsAdapter.enableAnalytics = function (config) { - if (!config.options.ci) { - logError('Client ID (ci) option is not defined. Analytics won\'t work'); - return; - } - - eplAnalyticsAdapter.context = { - events: [], - host: config.options.host || EPL_HOST, - ci: config.options.ci - }; - - eplAnalyticsAdapter.originEnableAnalytics(config); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: eplAnalyticsAdapter, - code: 'eplanning' -}); - -export default eplAnalyticsAdapter; diff --git a/modules/eplanningBidAdapter.js b/modules/eplanningBidAdapter.js index d57804c04e6..1fe5cb09c10 100644 --- a/modules/eplanningBidAdapter.js +++ b/modules/eplanningBidAdapter.js @@ -274,7 +274,7 @@ function getFloorStr(bid) { currency: DOLLAR_CODE, mediaType: '*', size: '*' - }); + }) || {}; if (bidFloor.floor) { return '|' + encodeURIComponent(bidFloor.floor); diff --git a/modules/equativBidAdapter.js b/modules/equativBidAdapter.js new file mode 100644 index 00000000000..7f59e46920e --- /dev/null +++ b/modules/equativBidAdapter.js @@ -0,0 +1,146 @@ +import { BANNER } from '../src/mediaTypes.js'; +import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { deepAccess, deepSetValue, mergeDeep } from '../src/utils.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + */ + +const BIDDER_CODE = 'equativ'; +const COOKIE_SYNC_ORIGIN = 'https://apps.smartadserver.com'; +const COOKIE_SYNC_URL = `${COOKIE_SYNC_ORIGIN}/diff/templates/asset/csync.html`; +const PID_COOKIE_NAME = 'eqt_pid'; + +export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + code: BIDDER_CODE, + gvlid: 45, + supportedMediaTypes: [BANNER], + + /** + * @param bidRequests + * @param bidderRequest + * @returns {ServerRequest[]} + */ + buildRequests: (bidRequests, bidderRequest) => { + return { + data: converter.toORTB({ bidderRequest, bidRequests }), + method: 'POST', + url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169' + }; + }, + + /** + * @param serverResponse + * @param bidRequest + * @returns {Bid[]} + */ + interpretResponse: (serverResponse, bidRequest) => + converter.fromORTB({ + request: bidRequest.data, + response: serverResponse.body, + }), + + /** + * @param bidRequest + * @returns {boolean} + */ + isBidRequestValid: (bidRequest) => { + return !!( + deepAccess(bidRequest, 'params.networkId') || + deepAccess(bidRequest, 'ortb2.site.publisher.id') || + deepAccess(bidRequest, 'ortb2.app.publisher.id') || + deepAccess(bidRequest, 'ortb2.dooh.publisher.id') + ); + }, + + /** + * @param syncOptions + * @returns {{type: string, url: string}[]} + */ + getUserSyncs: (syncOptions) => { + if (syncOptions.iframeEnabled) { + window.addEventListener('message', function handler(event) { + if (event.origin === COOKIE_SYNC_ORIGIN && event.data.pid) { + const exp = new Date(); + exp.setTime(Date.now() + 31536000000); // in a year + storage.setCookie(PID_COOKIE_NAME, event.data.pid, exp.toUTCString()); + this.removeEventListener('message', handler); + } + }); + + return [{ type: 'iframe', url: COOKIE_SYNC_URL }]; + } + + return []; + } +}; + +export const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: 300, + }, + + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const { siteId, pageId, formatId } = bidRequest.params; + + delete imp.dt; + + imp.bidfloor = imp.bidfloor || getBidFloor(bidRequest); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; + imp.tagid = bidRequest.adUnitCode; + + if (siteId || pageId || formatId) { + const bidder = {}; + + if (siteId) { + bidder.siteId = siteId; + } + + if (pageId) { + bidder.pageId = pageId; + } + + if (formatId) { + bidder.formatId = formatId; + } + + mergeDeep(imp, { + ext: { bidder }, + }); + } + + return imp; + }, + + request(buildRequest, imps, bidderRequest, context) { + const bid = context.bidRequests[0]; + const req = buildRequest(imps, bidderRequest, context); + + if (deepAccess(bid, 'ortb2.site.publisher')) { + deepSetValue(req, 'site.publisher.id', bid.ortb2.site.publisher.id || bid.params.networkId); + } else if (deepAccess(bid, 'ortb2.app.publisher')) { + deepSetValue(req, 'app.publisher.id', bid.ortb2.app.publisher.id || bid.params.networkId); + } else if (deepAccess(bid, 'ortb2.dooh.publisher')) { + deepSetValue(req, 'dooh.publisher.id', bid.ortb2.dooh.publisher.id || bid.params.networkId); + } else { + deepSetValue(req, 'site.publisher.id', bid.params.networkId); + } + + const pid = storage.getCookie(PID_COOKIE_NAME); + if (pid) { + deepSetValue(req, 'user.buyeruid', pid); + } + + return req; + }, +}); + +registerBidder(spec); diff --git a/modules/equativBidAdapter.md b/modules/equativBidAdapter.md new file mode 100644 index 00000000000..ceee6d19bdc --- /dev/null +++ b/modules/equativBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: Equativ Bidder Adapter (beta) +Module Type: Bidder Adapter +Maintainer: support@equativ.com +``` + +# Description + +Connect to Equativ for bids. + +The Equativ adapter requires setup and approval from the Equativ team. Please reach out to your technical account manager for more information. + +# Test Parameters + +## Web or In-app +```javascript +var adUnits = [ + { + code: '/589236/banner_1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [ + { + bidder: 'equativ', + params: { + networkId: 13, // mandatory if no ortb2.(site or app).publisher.id set + siteId: 20743, // optional + pageId: 89653, // optional + formatId: 291, // optional + } + } + ] + } +]; +``` \ No newline at end of file diff --git a/modules/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index ce01abb9e71..8b1beba09c3 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -2,16 +2,15 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import * as utils from '../src/utils.js'; -import {getBidIdParameter} from '../src/utils.js'; +import {getBidIdParameter, logInfo, mergeDeep} from '../src/utils.js'; /** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests */ const BIDDER_CODE = 'eskimi'; -// const ENDPOINT = 'https://hb.eskimi.com/bids' -const ENDPOINT = 'https://sspback.eskimi.com/bid-request' - const DEFAULT_BID_TTL = 30; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_NET_REVENUE = true; @@ -21,7 +20,7 @@ const VIDEO_ORTB_PARAMS = [ 'mimes', 'minduration', 'maxduration', - 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -37,7 +36,13 @@ const VIDEO_ORTB_PARAMS = [ const BANNER_ORTB_PARAMS = [ 'battr' -] +]; + +const REGION_SUBDOMAIN_SUFFIX = { + EU: '', + US: '-us-e', + APAC: '-asia' +}; export const spec = { code: BIDDER_CODE, @@ -46,15 +51,23 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, + getUserSyncs, /** * 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) { + logInfo('Bid won: ', bid); if (bid.burl) { utils.triggerPixel(bid.burl); } - } + }, + onTimeout: function (timeoutData) { + logInfo('Timeout: ', timeoutData); + }, + onBidderError: function ({error, bidderRequest}) { + logInfo('Error: ', error, bidderRequest); + }, } registerBidder(spec); @@ -67,7 +80,7 @@ const CONVERTER = ortbConverter({ }, imp(buildImp, bidRequest, context) { let imp = buildImp(bidRequest, context); - imp.secure = Number(window.location.protocol === 'https:'); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' @@ -80,7 +93,24 @@ const CONVERTER = ortbConverter({ } return imp; - } + }, + request(buildRequest, imps, bidderRequest, context) { + const req = buildRequest(imps, bidderRequest, context); + mergeDeep(req, { + at: 1, + ext: { + pv: '$prebid.version$' + } + }) + const bid = context.bidRequests[0]; + if (bid.params.coppa) { + utils.deepSetValue(req, 'regs.coppa', 1); + } + if (bid.params.test) { + req.test = 1 + } + return req; + }, }); function isBidRequestValid(bidRequest) { @@ -88,7 +118,7 @@ function isBidRequestValid(bidRequest) { } function isPlacementIdValid(bidRequest) { - return utils.isNumber(bidRequest.params.placementId); + return !!parseInt(bidRequest.params.placementId); } function isValidBannerRequest(bidRequest) { @@ -102,9 +132,17 @@ function isValidVideoRequest(bidRequest) { return utils.isArray(videoSizes) && videoSizes.length > 0 && videoSizes.every(size => utils.isNumber(size[0]) && utils.isNumber(size[1])); } -function buildRequests(validBids, bidderRequest) { - let videoBids = validBids.filter(bid => isVideoBid(bid)); - let bannerBids = validBids.filter(bid => isBannerBid(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. + */ +function buildRequests(validBidRequests, bidderRequest) { + let videoBids = validBidRequests.filter(bid => isVideoBid(bid)); + let bannerBids = validBidRequests.filter(bid => isBannerBid(bid)); let requests = []; bannerBids.forEach(bid => { @@ -119,14 +157,14 @@ function buildRequests(validBids, bidderRequest) { } function interpretResponse(response, request) { - return CONVERTER.fromORTB({ request: request.data, response: response.body }).bids; + return CONVERTER.fromORTB({request: request.data, response: response.body}).bids; } function buildVideoImp(bidRequest, imp) { const videoAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${VIDEO}`, {}); const videoBidderParams = utils.deepAccess(bidRequest, `params.${VIDEO}`, {}); - const videoParams = { ...videoAdUnitParams, ...videoBidderParams }; + const videoParams = {...videoAdUnitParams, ...videoBidderParams}; const videoSizes = (videoAdUnitParams && videoAdUnitParams.playerSize) || []; @@ -142,17 +180,17 @@ function buildVideoImp(bidRequest, imp) { }); if (imp.video && videoParams?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; + imp.video.plcmt = imp.video.plcmt || 4; } - return { ...imp }; + return {...imp}; } function buildBannerImp(bidRequest, imp) { const bannerAdUnitParams = utils.deepAccess(bidRequest, `mediaTypes.${BANNER}`, {}); const bannerBidderParams = utils.deepAccess(bidRequest, `params.${BANNER}`, {}); - const bannerParams = { ...bannerAdUnitParams, ...bannerBidderParams }; + const bannerParams = {...bannerAdUnitParams, ...bannerBidderParams}; let sizes = bidRequest.mediaTypes.banner.sizes; @@ -167,15 +205,15 @@ function buildBannerImp(bidRequest, imp) { } }); - return { ...imp }; + return {...imp}; } function createRequest(bidRequests, bidderRequest, mediaType) { - const data = CONVERTER.toORTB({ bidRequests, bidderRequest, context: { mediaType } }) + const data = CONVERTER.toORTB({bidRequests, bidderRequest, context: {mediaType}}) const bid = bidRequests.find((b) => b.params.placementId) if (!data.site) data.site = {} - data.site.ext = { placementId: bid.params.placementId } + data.site.ext = {placementId: parseInt(bid.params.placementId)} if (bidderRequest.gdprConsent) { if (!data.user) data.user = {}; @@ -192,9 +230,9 @@ function createRequest(bidRequests, bidderRequest, mediaType) { return { method: 'POST', - url: ENDPOINT, + url: getBidRequestUrlByRegion(), data: data, - options: { contentType: 'application/json;charset=UTF-8', withCredentials: false } + options: {contentType: 'application/json;charset=UTF-8', withCredentials: false} } } @@ -205,3 +243,84 @@ function isVideoBid(bid) { function isBannerBid(bid) { return utils.deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); } + +/** + * @param syncOptions + * @param responses + * @param gdprConsent + * @param uspConsent + * @param gppConsent + * @return {{type: (string), url: (*|string)}[]} + */ +function getUserSyncs(syncOptions, responses, gdprConsent, uspConsent, gppConsent) { + if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let query = []; + let syncUrl = getUserSyncUrlByRegion(); + // GDPR Consent Params in UserSync url + if (gdprConsent) { + query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); + query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + // US Privacy Consent + if (uspConsent) { + query.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + // Global Privacy Platform Consent + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + query.push('gpp=' + encodeURIComponent(gppConsent.gppString)); + query.push('gpp_sid=' + encodeURIComponent(gppConsent.applicableSections.join(','))); + } + return [{ + type: pixelType, + url: `${syncUrl}${query.length > 0 ? '&' + query.join('&') : ''}` + }]; + } +} + +/** + * Get Bid Request endpoint url by region + * @return {string} + */ +function getBidRequestUrlByRegion() { + return `https://ittr${getRegionSubdomainSuffix()}.eskimi.com/prebidjs`; +} + +/** + * Get User Sync endpoint url by region + * @return {string} + */ +function getUserSyncUrlByRegion() { + return `https://ittpx${getRegionSubdomainSuffix()}.eskimi.com/sync?sp_id=137`; +} + +/** + * Get subdomain URL suffix by region + * @return {string} + */ +function getRegionSubdomainSuffix() { + try { + const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; + const region = timezone.split('/')[0]; + + switch (region) { + case 'Europe': + case 'Africa': + case 'Atlantic': + case 'Arctic': + return REGION_SUBDOMAIN_SUFFIX['EU']; + case 'Asia': + case 'Australia': + case 'Antarctica': + case 'Pacific': + case 'Indian': + return REGION_SUBDOMAIN_SUFFIX['APAC']; + case 'America': + return REGION_SUBDOMAIN_SUFFIX['US']; + default: + return REGION_SUBDOMAIN_SUFFIX['EU']; + } + } catch (err) { + return REGION_SUBDOMAIN_SUFFIX['EU']; + } +} diff --git a/modules/eskimiBidAdapter.md b/modules/eskimiBidAdapter.md index b3494a217eb..30db251b7a8 100644 --- a/modules/eskimiBidAdapter.md +++ b/modules/eskimiBidAdapter.md @@ -48,6 +48,35 @@ Banner and video formats are supported. Where: -* placementId - Placement ID of the ad unit (required) -* bcat, badv, bapp, battr - ORTB blocking parameters as specified by OpenRTB 2.5 +* `placementId` - Placement ID of the ad unit (required) +* `bcat`, `badv`, `bapp`, `battr` - ORTB blocking parameters as specified by OpenRTB 2.5 +# ## Configuration + +Eskimi recommends the UserSync configuration below. Without it, the Eskimi adapter will not able to perform user syncs, which lowers match rate and reduces monetization. + +```javascript +pbjs.setConfig({ + userSync: { + filterSettings: { + iframe: { + bidders: ['eskimi'], + filter: 'include' + } + }, + syncDelay: 6000 + }}); +``` + +### Bidder Settings + +The Eskimi bid adapter uses browser local storage. Since Prebid.js 7.x, the access to it must be explicitly set. + +```js +// https://docs.prebid.org/dev-docs/publisher-api-reference/bidderSettings.html +pbjs.bidderSettings = { + eskimi: { + storageAllowed: true + } +} +``` diff --git a/modules/etargetBidAdapter.js b/modules/etargetBidAdapter.js index cced180e061..1653d849297 100644 --- a/modules/etargetBidAdapter.js +++ b/modules/etargetBidAdapter.js @@ -1,8 +1,9 @@ -import { deepSetValue, isFn, isPlainObject } from '../src/utils.js'; +import { deepClone, deepSetValue, isFn, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; const BIDDER_CODE = 'etarget'; +const GVL_ID = 29; const countryMap = { 1: 'sk', 2: 'cz', @@ -19,6 +20,7 @@ const countryMap = { } export const spec = { code: BIDDER_CODE, + gvlid: GVL_ID, supportedMediaTypes: [ BANNER, VIDEO ], isBidRequestValid: function (bid) { return !!(bid.params.refid && bid.params.country); @@ -26,7 +28,7 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { var i, l, bid, reqParams, netRevenue, gdprObject; var request = []; - var bids = JSON.parse(JSON.stringify(validBidRequests)); + var bids = deepClone(validBidRequests); var lastCountry = 'sk'; var floors = []; for (i = 0, l = bids.length; i < l; i++) { diff --git a/modules/euidIdSystem.js b/modules/euidIdSystem.js index d98dc02cdce..281fa04a12a 100644 --- a/modules/euidIdSystem.js +++ b/modules/euidIdSystem.js @@ -14,6 +14,13 @@ import {MODULE_TYPE_UID} from '../src/activities/modules.js'; // eslint-disable-next-line prebid/validate-imports import { Uid2GetId, Uid2CodeVersion, extractIdentityFromParams } from './uid2IdSystem_shared.js'; +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + const MODULE_NAME = 'euid'; const MODULE_REVISION = Uid2CodeVersion; const PREBID_VERSION = '$prebid.version$'; @@ -75,9 +82,9 @@ export const euidIdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [configparams] + * @param {SubmoduleConfig} [config] * @param {ConsentData|undefined} consentData - * @returns {euidId} + * @returns {IdResponse} */ getId(config, consentData) { if (consentData?.gdprApplies !== true) { @@ -103,7 +110,6 @@ export const euidIdSubmodule = { mappedConfig.cstg = { serverPublicKey: config?.params?.serverPublicKey, subscriptionId: config?.params?.subscriptionId, - optoutCheck: 1, ...extractIdentityFromParams(config?.params ?? {}) } } diff --git a/modules/excoBidAdapter.js b/modules/excoBidAdapter.js new file mode 100644 index 00000000000..150d467a08a --- /dev/null +++ b/modules/excoBidAdapter.js @@ -0,0 +1,37 @@ +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {getStorageManager} from '../src/storageManager.js'; +import { + isBidRequestValid, createUserSyncGetter, createInterpretResponseFn, createBuildRequestsFn +} from '../libraries/vidazooUtils/bidderUtils.js'; + +const DEFAULT_SUB_DOMAIN = 'rtb'; +const BIDDER_CODE = 'exco'; +const BIDDER_VERSION = '1.0.0'; +const GVLID = 444; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); + +export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { + return `https://${subDomain}.exco-pb.com`; +} + +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); + +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); + +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://cs.exco-pb.com/api/sync/iframe', imageSyncUrl: 'https://cs.exco-pb.com/api/sync/image' +}); + +export const spec = { + code: BIDDER_CODE, + version: BIDDER_VERSION, + supportedMediaTypes: [BANNER, VIDEO], + gvlid: GVLID, + isBidRequestValid, + buildRequests, + interpretResponse, + getUserSyncs +}; + +registerBidder(spec); diff --git a/modules/minutemediaplusBidAdapter.md b/modules/excoBidAdapter.md similarity index 71% rename from modules/minutemediaplusBidAdapter.md rename to modules/excoBidAdapter.md index 410c00e7017..f171de73883 100644 --- a/modules/minutemediaplusBidAdapter.md +++ b/modules/excoBidAdapter.md @@ -1,24 +1,24 @@ # Overview -**Module Name:** MinuteMediaPlus Bidder Adapter +**Module Name:** Exco Bid Adapter **Module Type:** Bidder Adapter -**Maintainer:** hb@minutemedia.com +**Maintainer:** Itadmin@ex.co # Description -Module that connects to MinuteMediaPlus's demand sources. - +Module that connects to Exco's demand sources. + # Test Parameters -```js + ```js var adUnits = [ { code: 'test-ad', sizes: [[300, 250]], bids: [ { - bidder: 'mmplus', + bidder: 'exco', params: { cId: '562524b21b1c1f08117fc7f9', pId: '59ac17c192832d0011283fe3', diff --git a/modules/fanAdapter.js b/modules/fanAdapter.js new file mode 100644 index 00000000000..cdcc8d19889 --- /dev/null +++ b/modules/fanAdapter.js @@ -0,0 +1,176 @@ +import * as utils from '../src/utils.js'; +import { ajax } from '../src/ajax.js'; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'freedomadnetwork'; +const BASE_URL = 'https://srv.freedomadnetwork.com'; + +/** + * Build OpenRTB request from bidRequest and bidderRequest + * + * @param {BidRequest} bid + * @param {BidderRequest} bidderRequest + * @returns {Request} + */ +function buildBidRequest(bid, bidderRequest) { + const payload = { + id: bid.bidId, + tmax: bidderRequest.timeout, + placements: [bid.params.placementId], + at: 1, + user: {} + } + + const gdprConsent = utils.deepAccess(bidderRequest, 'gdprConsent'); + if (!!gdprConsent && gdprConsent.gdprApplies) { + payload.user.gdpr = 1; + payload.user.consent = gdprConsent.consentString; + } + + const uspConsent = utils.deepAccess(bidderRequest, 'uspConsent'); + if (uspConsent) { + payload.user.usp = uspConsent; + } + + return { + method: 'POST', + url: BASE_URL + '/pb/req', + data: JSON.stringify(payload), + options: { + contentType: 'application/json', + withCredentials: false, + customHeaders: { + 'Accept-Language': 'en;q=10', + }, + }, + originalBidRequest: bid + } +} + +export const spec = { + code: BIDDER_CODE, + 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; + } + + var banner = utils.deepAccess(bid, 'mediaTypes.banner'); + if (banner === undefined) { + return false; + } + + return true; + }, + + buildRequests: function(validBidRequests, bidderRequest) { + return validBidRequests.map(bid => buildBidRequest(bid, 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; + let bidResponses = []; + + if (!serverBody) { + return bidResponses; + } + + serverBody.forEach((response) => { + const bidResponse = { + requestId: response.id, + bidid: response.bidid, + impid: response.impid, + userId: response.userId, + cpm: response.cpm, + currency: response.currency, + width: response.width, + height: response.height, + ad: response.payload, + ttl: response.ttl, + creativeId: response.crid, + netRevenue: response.netRevenue, + trackers: response.trackers, + meta: { + mediaType: response.mediaType, + advertiserDomains: response.domains, + } + }; + + bidResponses.push(bidResponse); + }); + + return bidResponses; + }, + + /** + * 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) { + return; + } + + const payload = { + id: bid.bidid, + impid: bid.impid, + t: bid.cpm, + u: bid.userId, + } + + ajax(BASE_URL + '/pb/imp', null, JSON.stringify(payload), { + method: 'POST', + customHeaders: { + 'Accept-Language': 'en;q=10', + }, + }); + + if (bid.trackers && bid.trackers.length > 0) { + for (var i = 0; i < bid.trackers.length; i++) { + if (bid.trackers[i].type == 0) { + utils.triggerPixel(bid.trackers[i].url); + } + } + } + }, + onSetTargeting: function(bid) {}, + onBidderError: function(error) { + utils.logError(`${BIDDER_CODE} bidder error`, error); + }, + getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { + const syncs = []; + return syncs; + }, + onTimeout: function(timeoutData) {}, + supportedMediaTypes: [BANNER, NATIVE] +} + +registerBidder(spec); diff --git a/modules/fanAdapter.md b/modules/fanAdapter.md new file mode 100644 index 00000000000..caa18ca8552 --- /dev/null +++ b/modules/fanAdapter.md @@ -0,0 +1,40 @@ +# Freedom Ad Network Bidder Adapter + +# Overview + +``` +Module Name: Freedom Ad Network Bidder Adapter +Module Type: Bidder Adapter +Maintainer: info@freedomadnetwork.com +``` + +## Description + +Module that connects to FAN's demand sources. + +## Bid Parameters + +| Name | Scope | Type | Description | Example | +|---------------|----------|--------------------|-----------------------------------------|-------------------------------------------------| +| `placementId` | required | String | The Placement Id provided by FAN. | `e6203f1e-bd6d-4f42-9895-d1a19cdb83c8` | + +## Example + +### Banner Ads + +```javascript +var adUnits = [{ + code: 'banner-ad-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'freedomadnetwork', + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + }] +}]; +``` diff --git a/modules/finativeBidAdapter.js b/modules/finativeBidAdapter.js index 87580a209bb..e7613bf9cce 100644 --- a/modules/finativeBidAdapter.js +++ b/modules/finativeBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {NATIVE} from '../src/mediaTypes.js'; -import {_map, deepAccess, deepSetValue, isEmpty} from '../src/utils.js'; +import {_map, deepSetValue, isEmpty, setOnAny} from '../src/utils.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; @@ -224,15 +224,6 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - function flatten(arr) { return [].concat(...arr); } diff --git a/modules/fintezaAnalyticsAdapter.js b/modules/fintezaAnalyticsAdapter.js index ab41272c85f..78777cd6478 100644 --- a/modules/fintezaAnalyticsAdapter.js +++ b/modules/fintezaAnalyticsAdapter.js @@ -70,7 +70,8 @@ function initFirstVisit() { let cookies; try { - cookies = parseCookies(document.cookie); + // TODO: commented out because of rule violations + cookies = {} // parseCookies(document.cookie); } catch (a) { cookies = {}; } @@ -91,7 +92,8 @@ function initFirstVisit() { return visitDate; } - +// TODO: commented out because of rule violations +/* function trim(string) { if (string.trim) { return string.trim(); @@ -130,6 +132,7 @@ function parseCookies(cookie) { return values; } +*/ function getRandAsStr(digits) { let str = ''; @@ -172,7 +175,8 @@ function initSession() { let isNew = false; try { - cookies = parseCookies(document.cookie); + // TODO: commented out because of rule violations + cookies = {} // parseCookies(document.cookie); } catch (a) { cookies = {}; } @@ -263,7 +267,8 @@ function getTrackRequestLastTime() { ); } - cookie = parseCookies(document.cookie); + // TODO: commented out because of rule violations + cookie = {} // parseCookies(document.cookie); cookie = cookie[ TRACK_TIME_KEY ]; if (cookie) { return parseInt(cookie, 10); diff --git a/modules/fledgeForGpt.js b/modules/fledgeForGpt.js deleted file mode 100644 index bda4494faaf..00000000000 --- a/modules/fledgeForGpt.js +++ /dev/null @@ -1,109 +0,0 @@ -/** - * GPT-specific slot configuration logic for PAAPI. - */ -import {submodule} from '../src/hook.js'; -import {deepAccess, logInfo, logWarn} from '../src/utils.js'; -import {getGptSlotForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; - -// import parent module to keep backwards-compat for NPM consumers after paapi was split from fledgeForGpt -// there's a special case in webpack.conf.js to avoid duplicating build output on non-npm builds -// TODO: remove this in prebid 9 -// eslint-disable-next-line prebid/validate-imports -import './paapi.js'; -const MODULE = 'fledgeForGpt'; - -let getPAAPIConfig; - -// for backwards compat, we attempt to automatically set GPT configuration as soon as we -// have the auction configs available. Disabling this allows one to call pbjs.setPAAPIConfigForGPT at their -// own pace. -let autoconfig = true; - -Object.entries({ - [MODULE]: MODULE, - 'paapi': 'paapi.gpt' -}).forEach(([topic, ns]) => { - const configKey = `${ns}.autoconfig`; - config.getConfig(topic, (cfg) => { - autoconfig = deepAccess(cfg, configKey, true); - }); -}); - -export function slotConfigurator() { - const PREVIOUSLY_SET = {}; - return function setComponentAuction(adUnitCode, auctionConfigs, reset = true) { - const gptSlot = getGptSlotForAdUnitCode(adUnitCode); - if (gptSlot && gptSlot.setConfig) { - let previous = PREVIOUSLY_SET[adUnitCode] ?? {}; - let configsBySeller = Object.fromEntries(auctionConfigs.map(cfg => [cfg.seller, cfg])); - const sellers = Object.keys(configsBySeller); - if (reset) { - configsBySeller = Object.assign(previous, configsBySeller); - previous = Object.fromEntries(sellers.map(seller => [seller, null])); - } else { - sellers.forEach(seller => { - previous[seller] = null; - }); - } - Object.keys(previous).length ? PREVIOUSLY_SET[adUnitCode] = previous : delete PREVIOUSLY_SET[adUnitCode]; - const componentAuction = Object.entries(configsBySeller) - .map(([configKey, auctionConfig]) => ({configKey, auctionConfig})); - if (componentAuction.length > 0) { - gptSlot.setConfig({componentAuction}); - logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); - } - } else if (auctionConfigs.length > 0) { - logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); - } - }; -} - -const setComponentAuction = slotConfigurator(); - -export function onAuctionConfigFactory(setGptConfig = setComponentAuction) { - return function onAuctionConfig(auctionId, configsByAdUnit, markAsUsed) { - if (autoconfig) { - Object.entries(configsByAdUnit).forEach(([adUnitCode, cfg]) => { - setGptConfig(adUnitCode, cfg?.componentAuctions ?? []); - markAsUsed(adUnitCode); - }); - } - } -} - -export function setPAAPIConfigFactory( - getConfig = (filters) => getPAAPIConfig(filters, true), - setGptConfig = setComponentAuction) { - /** - * Configure GPT slots with PAAPI auction configs. - * `filters` are the same filters accepted by `pbjs.getPAAPIConfig`; - */ - return function(filters = {}) { - let some = false; - Object.entries( - getConfig(filters) || {} - ).forEach(([au, config]) => { - if (config != null) { - some = true; - } - setGptConfig(au, config?.componentAuctions || [], true); - }) - if (!some) { - logInfo(`${MODULE}: No component auctions available to set`); - } - } -} -/** - * Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`. - */ -getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory(); - -submodule('paapi', { - name: 'gpt', - onAuctionConfig: onAuctionConfigFactory(), - init(params) { - getPAAPIConfig = params.getPAAPIConfig; - } -}); diff --git a/modules/flippBidAdapter.js b/modules/flippBidAdapter.js index f9c424d9da5..95fd67c779b 100644 --- a/modules/flippBidAdapter.js +++ b/modules/flippBidAdapter.js @@ -25,6 +25,7 @@ const DEFAULT_CREATIVE_TYPE = 'NativeX'; const VALID_CREATIVE_TYPES = ['DTX', 'NativeX']; const FLIPP_USER_KEY = 'flipp-uid'; const COMPACT_DEFAULT_HEIGHT = 600; +const STANDARD_DEFAULT_HEIGHT = 1800; let userKey = null; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); @@ -166,7 +167,10 @@ export const spec = { if (!isEmpty(res) && !isEmpty(res.decisions) && !isEmpty(res.decisions.inline)) { return res.decisions.inline.map(decision => { const placement = placements.find(p => p.prebid.requestId === decision.prebid?.requestId); - const height = placement.options?.startCompact ? COMPACT_DEFAULT_HEIGHT : decision.height; + const customData = decision.contents[0]?.data?.customData; + const height = placement.options?.startCompact + ? customData?.compactHeight ?? COMPACT_DEFAULT_HEIGHT + : customData?.standardHeight ?? STANDARD_DEFAULT_HEIGHT; return { bidderCode: BIDDER_CODE, requestId: decision.prebid?.requestId, diff --git a/modules/fpdModule/index.md b/modules/fpdModule/index.md index 238881db96b..42ae663b090 100644 --- a/modules/fpdModule/index.md +++ b/modules/fpdModule/index.md @@ -44,6 +44,5 @@ pbjs.setConfig({ At least one of the submodules must be included in order to successfully run the corresponding above operations. -enrichmentFpdModule validationFpdModule -topicsFpdModule \ No newline at end of file +topicsFpdModule diff --git a/modules/freewheel-sspBidAdapter.js b/modules/freewheel-sspBidAdapter.js index e11aa3f8fb7..fc85edc483b 100644 --- a/modules/freewheel-sspBidAdapter.js +++ b/modules/freewheel-sspBidAdapter.js @@ -76,7 +76,7 @@ function getPricing(xmlNode) { var priceNode = pricingExtNode.querySelector('Price'); princingData = { currency: priceNode.getAttribute('currency'), - price: priceNode.textContent || priceNode.innerText + price: priceNode.textContent }; } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing pricing extension.'); @@ -110,7 +110,7 @@ function getAdvertiserDomain(xmlNode) { // Currently we only return one Domain if (brandExtNode) { var domainNode = brandExtNode.querySelector('Domain'); - domain.push(domainNode.textContent || domainNode.innerText); + domain.push(domainNode.textContent); } else { logWarn('PREBID - ' + BIDDER_CODE + ': No bid received or missing StickyBrand extension.'); } @@ -231,7 +231,7 @@ function getBidFloor(bid, config) { mediaType: typeof bid.mediaTypes['banner'] == 'object' ? 'banner' : 'video', size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } catch (e) { return -1; } diff --git a/modules/ftrackIdSystem.js b/modules/ftrackIdSystem.js index 1794c3f76f4..fa6c7d4050c 100644 --- a/modules/ftrackIdSystem.js +++ b/modules/ftrackIdSystem.js @@ -142,7 +142,7 @@ export const ftrackIdSubmodule = { } // Creates an async script element and appends it to the document - loadExternalScript(config.params.url, MODULE_NAME); + loadExternalScript(config.params.url, MODULE_TYPE_UID, MODULE_NAME); } }; }, diff --git a/modules/gameraRtdProvider.js b/modules/gameraRtdProvider.js new file mode 100644 index 00000000000..96c4bac5f87 --- /dev/null +++ b/modules/gameraRtdProvider.js @@ -0,0 +1,107 @@ +import { submodule } from '../src/hook.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { + isPlainObject, + logError, + mergeDeep, + deepClone, +} from '../src/utils.js'; + +const MODULE_NAME = 'gamera'; +const MODULE = `${MODULE_NAME}RtdProvider`; + +/** + * Initialize the Gamera RTD Module. + * @param {Object} config + * @param {Object} userConsent + * @returns {boolean} + */ +function init(config, userConsent) { + return true; +} + +/** + * Modify bid request data before auction + * @param {Object} reqBidsConfigObj - The bid request config object + * @param {function} callback - Callback function to execute after data handling + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + */ +function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + // Check if window.gamera.getPrebidSegments is available + if (typeof window.gamera?.getPrebidSegments !== 'function') { + window.gamera = window.gamera || {}; + window.gamera.cmd = window.gamera.cmd || []; + window.gamera.cmd.push(function () { + enrichAuction(reqBidsConfigObj, callback, config, userConsent); + }); + return; + } + + enrichAuction(reqBidsConfigObj, callback, config, userConsent); +} + +/** + * Enriches the auction with user and content segments from Gamera's on-page script + * @param {Object} reqBidsConfigObj - The bid request config object + * @param {Function} callback - Callback function to execute after data handling + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + */ +function enrichAuction(reqBidsConfigObj, callback, config, userConsent) { + try { + /** + * @function external:"window.gamera".getPrebidSegments + * @description Retrieves user and content segments from Gamera's on-page script + * @param {Function|null} onSegmentsUpdateCallback - Callback for segment updates (not used here) + * @param {Object} config - Module configuration + * @param {Object} userConsent - User consent data + * @returns {Object|undefined} segments - The targeting segments object containing: + * @property {Object} [user] - User-level attributes to merge into ortb2.user + * @property {Object} [site] - Site-level attributes to merge into ortb2.site + * @property {Object.} [adUnits] - Ad unit specific attributes, keyed by adUnitCode, + * to merge into each ad unit's ortb2Imp + */ + const segments = window.gamera.getPrebidSegments(null, deepClone(config || {}), deepClone(userConsent || {})) || {}; + + // Initialize ortb2Fragments and its nested objects + reqBidsConfigObj.ortb2Fragments = reqBidsConfigObj.ortb2Fragments || {}; + reqBidsConfigObj.ortb2Fragments.global = reqBidsConfigObj.ortb2Fragments.global || {}; + + // Add user-level data + if (segments.user && isPlainObject(segments.user)) { + reqBidsConfigObj.ortb2Fragments.global.user = reqBidsConfigObj.ortb2Fragments.global.user || {}; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global.user, segments.user); + } + + // Add site-level data + if (segments.site && isPlainObject(segments.site)) { + reqBidsConfigObj.ortb2Fragments.global.site = reqBidsConfigObj.ortb2Fragments.global.site || {}; + mergeDeep(reqBidsConfigObj.ortb2Fragments.global.site, segments.site); + } + + // Add adUnit-level data + const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits || []; + adUnits.forEach(adUnit => { + const gameraData = segments.adUnits && segments.adUnits[adUnit.code]; + if (!gameraData || !isPlainObject(gameraData)) { + return; + } + + adUnit.ortb2Imp = adUnit.ortb2Imp || {}; + mergeDeep(adUnit.ortb2Imp, gameraData); + }); + } catch (error) { + logError(MODULE, 'Error getting segments:', error); + } + + callback(); +} + +export const subModuleObj = { + name: MODULE_NAME, + init: init, + getBidRequestData: getBidRequestData, +}; + +submodule('realTimeData', subModuleObj); diff --git a/modules/gameraRtdProvider.md b/modules/gameraRtdProvider.md new file mode 100644 index 00000000000..44260b88d1f --- /dev/null +++ b/modules/gameraRtdProvider.md @@ -0,0 +1,51 @@ +# Overview + +Module Name: Gamera Rtd Provider +Module Type: Rtd Provider +Maintainer: aleksa@gamera.ai + +# Description + +RTD provider for Gamera.ai that enriches bid requests with real-time data, by populating the [First Party Data](https://docs.prebid.org/features/firstPartyData.html) attributes. +The module integrates with Gamera's AI-powered audience segmentation system to provide enhanced bidding capabilities. +The Gamera RTD Provider works in conjunction with the Gamera script, which must be available on the page for the module to enrich bid requests. To learn more about the Gamera script, please visit the [Gamera website](https://gamera.ai/). + +ORTB2 enrichments that gameraRtdProvider can provide: + * `ortb2.site` + * `ortb2.user` + * `AdUnit.ortb2Imp` + +# Integration + +## Build + +Include the Gamera RTD module in your Prebid.js build: + +```bash +gulp build --modules=rtdModule,gameraRtdProvider +``` + +## Configuration + +Configure the module in your Prebid.js configuration: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'gamera', + params: { + // Optional configuration parameters + } + }] + } +}); +``` + +### Configuration Parameters + +The module currently supports basic initialization without required parameters. Future versions may include additional configuration options. + +## Support + +For more information or support, please contact gareth@gamera.ai. diff --git a/modules/gamoshiBidAdapter.js b/modules/gamoshiBidAdapter.js index 1c279cdb9b8..cfc2e32ca67 100644 --- a/modules/gamoshiBidAdapter.js +++ b/modules/gamoshiBidAdapter.js @@ -157,7 +157,7 @@ export const spec = { 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, + plcmt: bidRequest.mediaTypes.video.plcmt || bidRequest.params.video.plcmt, 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 diff --git a/modules/genericAnalyticsAdapter.js b/modules/genericAnalyticsAdapter.js index 7f721863912..ce37e5c02fe 100644 --- a/modules/genericAnalyticsAdapter.js +++ b/modules/genericAnalyticsAdapter.js @@ -142,7 +142,7 @@ export function defaultHandler({url, method, batchSize, ajax = ajaxBuilder()}) { const serialize = method === 'GET' ? (data) => ({data: JSON.stringify(data)}) : (data) => JSON.stringify(data); return function (events) { - ajax(url, callbacks, serialize(extract(events)), {method}) + ajax(url, callbacks, serialize(extract(events)), {method, keepalive: true}) } } diff --git a/modules/geoedgeRtdProvider.js b/modules/geoedgeRtdProvider.js index 46f7e7f7d6d..09e717a112f 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -23,6 +23,7 @@ import { EVENTS } from '../src/constants.js'; import { loadExternalScript } from '../src/adloader.js'; import { auctionManager } from '../src/auctionManager.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -53,6 +54,10 @@ export let wrapper let wrapperReady; /** @type {boolean} */; let preloaded; +/** @type {object} */; +let refererInfo = getRefererInfo(); +/** @type {object} */; +let overrides = window.grumi?.overrides; /** * fetches the creative wrapper @@ -75,9 +80,8 @@ export function setWrapper(responseText) { } export function getInitialParams(key) { - let refererInfo = getRefererInfo(); let params = { - wver: 'pbjs', + wver: '1.1.1', wtype: 'pbjs-module', key, meta: { @@ -105,7 +109,7 @@ export function preloadClient(key) { insertElement(iframe); iframe.contentWindow.grumi = getInitialParams(key); let url = getClientUrl(key); - loadExternalScript(url, SUBMODULE_NAME, markAsLoaded, iframe.contentDocument); + loadExternalScript(url, MODULE_TYPE_RTD, SUBMODULE_NAME, markAsLoaded, iframe.contentDocument); } /** @@ -141,7 +145,7 @@ export function getMacros(bid, key) { '%_hbcid!': bid.creativeId || '', '%_hbadomains': bid.meta && bid.meta.advertiserDomains, '%%PATTERN:hb_pb%%': bid.pbHg, - '%%SITE%%': location.hostname, + '%%SITE%%': overrides?.site || refererInfo.domain, '%_pimp%': PV_ID, '%_hbCpm!': bid.cpm, '%_hbCurrency!': bid.currency @@ -256,7 +260,7 @@ function fireBillableEventsForApplicableBids(params) { function setupInPage(params) { window.grumi = params; window.grumi.fromPrebid = true; - loadExternalScript(getInPageUrl(params.key), SUBMODULE_NAME); + loadExternalScript(getInPageUrl(params.key), MODULE_TYPE_RTD, SUBMODULE_NAME); } function init(config, userConsent) { diff --git a/modules/getintentBidAdapter.js b/modules/getintentBidAdapter.js index a8888893333..67a0e1e91be 100644 --- a/modules/getintentBidAdapter.js +++ b/modules/getintentBidAdapter.js @@ -152,7 +152,7 @@ function getBidFloor(bidRequest, currency) { currency: currency || DEFAULT_CURRENCY, mediaType: bidRequest.mediaType, size: bidRequest.sizes || '*' - }); + }) || {}; } return { diff --git a/modules/globalsunBidAdapter.js b/modules/globalsunBidAdapter.js index 5b5d97c2cac..1684509b7b9 100644 --- a/modules/globalsunBidAdapter.js +++ b/modules/globalsunBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'globalsun'; const AD_URL = 'https://endpoint.globalsun.io/pbjs'; const SYNC_URL = 'https://cs.globalsun.io'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/goldbachBidAdapter.js b/modules/goldbachBidAdapter.js index df0336c6cd4..07ad6b2fa97 100644 --- a/modules/goldbachBidAdapter.js +++ b/modules/goldbachBidAdapter.js @@ -21,7 +21,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ADPOD, BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; @@ -742,7 +742,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords); - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/gothamadsBidAdapter.js b/modules/gothamadsBidAdapter.js index ab59c6febec..bcd382e507a 100644 --- a/modules/gothamadsBidAdapter.js +++ b/modules/gothamadsBidAdapter.js @@ -1,4 +1,4 @@ -import { logMessage, deepSetValue, deepAccess, _map, logWarn } from '../src/utils.js'; +import { deepSetValue, deepAccess, _map, logWarn } 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'; @@ -80,18 +80,9 @@ export const spec = { if (validBidRequests && validBidRequests.length === 0) return [] let accuontId = validBidRequests[0].params.accountId; const endpointURL = URL_ENDPOINT.replace(ACCOUNTID_MACROS, accuontId); - let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; let bids = []; for (let bidRequest of validBidRequests) { let impObject = prepareImpObject(bidRequest); @@ -105,8 +96,8 @@ export const spec = { language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', }, site: { - page: location.pathname, - host: location.host + page: location?.page, + host: location?.domain }, source: { tid: bidderRequest?.ortb2?.source?.tid, @@ -332,7 +323,7 @@ const parseSizes = (bid, mediaType) => { const addVideoParameters = (bidRequest) => { let videoObj = {}; - let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] + let supportParamsList = ['mimes', 'minduration', 'maxduration', 'protocols', 'startdelay', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'] for (let param of supportParamsList) { if (bidRequest.mediaTypes.video[param] !== undefined) { diff --git a/modules/gptPreAuction.js b/modules/gptPreAuction.js index bf5b4a55dbb..a6495e3570e 100644 --- a/modules/gptPreAuction.js +++ b/modules/gptPreAuction.js @@ -1,19 +1,68 @@ +import { getSignals as getSignalsFn, getSegments as getSegmentsFn, taxonomies } from '../libraries/gptUtils/gptUtils.js'; +import { auctionManager } from '../src/auctionManager.js'; +import { config } from '../src/config.js'; +import { TARGETING_KEYS } from '../src/constants.js'; +import { getHook } from '../src/hook.js'; +import { find } from '../src/polyfill.js'; import { deepAccess, + deepSetValue, isAdUnitCodeMatchingSlot, isGptPubadsDefined, logInfo, + logWarn, pick, - deepSetValue + uniques } from '../src/utils.js'; -import {config} from '../src/config.js'; -import {getHook} from '../src/hook.js'; -import {find} from '../src/polyfill.js'; const MODULE_NAME = 'GPT Pre-Auction'; export let _currentConfig = {}; let hooksAdded = false; +export function getSegments(fpd, sections, segtax) { + return getSegmentsFn(fpd, sections, segtax); +} + +export function getSignals(fpd) { + return getSignalsFn(fpd); +} + +export function getSignalsArrayByAuctionsIds(auctionIds, index = auctionManager.index) { + const signals = auctionIds + .map(auctionId => index.getAuction({ auctionId })?.getFPD()?.global) + .map(getSignals) + .filter(fpd => fpd); + + return signals; +} + +export function getSignalsIntersection(signals) { + const result = {}; + taxonomies.forEach((taxonomy) => { + const allValues = signals + .flatMap(x => x) + .filter(x => x.taxonomy === taxonomy) + .map(x => x.values); + result[taxonomy] = allValues.length ? ( + allValues.reduce((commonElements, subArray) => { + return commonElements.filter(element => subArray.includes(element)); + }) + ) : [] + result[taxonomy] = { values: result[taxonomy] }; + }) + return result; +} + +export function getAuctionsIdsFromTargeting(targeting, am = auctionManager) { + return Object.values(targeting) + .flatMap(x => Object.entries(x)) + .filter((entry) => entry[0] === TARGETING_KEYS.AD_ID || entry[0].startsWith(TARGETING_KEYS.AD_ID + '_')) + .flatMap(entry => entry[1]) + .map(adId => am.findBidByAdId(adId)?.auctionId) + .filter(id => id != null) + .filter(uniques); +} + export const appendGptSlots = adUnits => { const { customGptSlotMatching } = _currentConfig; @@ -27,21 +76,25 @@ export const appendGptSlots = adUnits => { return acc; }, {}); + const adUnitPaths = {}; + window.googletag.pubads().getSlots().forEach(slot => { const matchingAdUnitCode = find(Object.keys(adUnitMap), customGptSlotMatching ? customGptSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)); if (matchingAdUnitCode) { + const path = adUnitPaths[matchingAdUnitCode] = slot.getAdUnitPath(); const adserver = { name: 'gam', - adslot: sanitizeSlotPath(slot.getAdUnitPath()) + adslot: sanitizeSlotPath(path) }; adUnitMap[matchingAdUnitCode].forEach((adUnit) => { deepSetValue(adUnit, 'ortb2Imp.ext.data.adserver', Object.assign({}, adUnit.ortb2Imp?.ext?.data?.adserver, adserver)); }); } }); + return adUnitPaths; }; const sanitizeSlotPath = (path) => { @@ -54,7 +107,7 @@ const sanitizeSlotPath = (path) => { return path; } -const defaultPreAuction = (adUnit, adServerAdSlot) => { +const defaultPreAuction = (adUnit, adServerAdSlot, adUnitPath) => { const context = adUnit.ortb2Imp.ext.data; // use pbadslot if supplied @@ -68,7 +121,7 @@ const defaultPreAuction = (adUnit, adServerAdSlot) => { } // find all GPT slots with this name - var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adServerAdSlot); + var gptSlots = window.googletag.pubads().getSlots().filter(slot => slot.getAdUnitPath() === adUnitPath); if (gptSlots.length === 0) { return; // should never happen @@ -113,8 +166,12 @@ export const appendPbAdSlot = adUnit => { return true; }; +function warnDeprecation(adUnit) { + logWarn(`pbadslot is deprecated and will soon be removed, use gpid instead`, adUnit) +} + export const makeBidRequestsHook = (fn, adUnits, ...args) => { - appendGptSlots(adUnits); + const adUnitPaths = appendGptSlots(adUnits); const { useDefaultPreAuction, customPreAuction } = _currentConfig; adUnits.forEach(adUnit => { // init the ortb2Imp if not done yet @@ -122,21 +179,24 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { adUnit.ortb2Imp.ext = adUnit.ortb2Imp.ext || {}; adUnit.ortb2Imp.ext.data = adUnit.ortb2Imp.ext.data || {}; const context = adUnit.ortb2Imp.ext; - // if neither new confs set do old stuff if (!customPreAuction && !useDefaultPreAuction) { + warnDeprecation(adUnit); const usedAdUnitCode = appendPbAdSlot(adUnit); // gpid should be set to itself if already set, or to what pbadslot was (as long as it was not adUnit code) if (!context.gpid && !usedAdUnitCode) { context.gpid = context.data.pbadslot; } } else { + if (context.data?.pbadslot) { + warnDeprecation(adUnit); + } let adserverSlot = deepAccess(context, 'data.adserver.adslot'); let result; if (customPreAuction) { - result = customPreAuction(adUnit, adserverSlot); + result = customPreAuction(adUnit, adserverSlot, adUnitPaths?.[adUnit.code]); } else if (useDefaultPreAuction) { - result = defaultPreAuction(adUnit, adserverSlot); + result = defaultPreAuction(adUnit, adserverSlot, adUnitPaths?.[adUnit.code]); } if (result) { context.gpid = context.data.pbadslot = result; @@ -146,6 +206,14 @@ export const makeBidRequestsHook = (fn, adUnits, ...args) => { return fn.call(this, adUnits, ...args); }; +const setPpsConfigFromTargetingSet = (next, targetingSet) => { + // set gpt config + const auctionsIds = getAuctionsIdsFromTargeting(targetingSet); + const signals = getSignalsIntersection(getSignalsArrayByAuctionsIds(auctionsIds)); + window.googletag.setConfig && window.googletag.setConfig({pps: { taxonomies: signals }}); + next(targetingSet); +}; + const handleSetGptConfig = moduleConfig => { _currentConfig = pick(moduleConfig, [ 'enabled', enabled => enabled !== false, @@ -153,18 +221,20 @@ const handleSetGptConfig = moduleConfig => { typeof customGptSlotMatching === 'function' && customGptSlotMatching, 'customPbAdSlot', customPbAdSlot => typeof customPbAdSlot === 'function' && customPbAdSlot, 'customPreAuction', customPreAuction => typeof customPreAuction === 'function' && customPreAuction, - 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction === true, + 'useDefaultPreAuction', useDefaultPreAuction => useDefaultPreAuction ?? true, ]); if (_currentConfig.enabled) { if (!hooksAdded) { getHook('makeBidRequests').before(makeBidRequestsHook); + getHook('targetingDone').after(setPpsConfigFromTargetingSet) hooksAdded = true; } } else { logInfo(`${MODULE_NAME}: Turning off module`); _currentConfig = {}; getHook('makeBidRequests').getHooks({hook: makeBidRequestsHook}).remove(); + getHook('targetingDone').getHooks({hook: setPpsConfigFromTargetingSet}).remove(); hooksAdded = false; } }; diff --git a/modules/greenbidsAnalyticsAdapter.js b/modules/greenbidsAnalyticsAdapter.js index d6293adac82..99ce89ee4d1 100644 --- a/modules/greenbidsAnalyticsAdapter.js +++ b/modules/greenbidsAnalyticsAdapter.js @@ -4,9 +4,17 @@ import { EVENTS } from '../src/constants.js'; import adapterManager from '../src/adapterManager.js'; import {deepClone, generateUUID, logError, logInfo, logWarn, getParameterByName} from '../src/utils.js'; +/** + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + */ + +/** + * @typedef {object} Message Payload message sent to the Greenbids API + */ + const analyticsType = 'endpoint'; -export const ANALYTICS_VERSION = '2.2.1'; +export const ANALYTICS_VERSION = '2.3.2'; const ANALYTICS_SERVER = 'https://a.greenbids.ai'; @@ -97,6 +105,11 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER contentType: 'application/json' }); }, + /** + * + * @param {string} auctionId + * @returns {Message} + */ createCommonMessage(auctionId) { const cachedAuction = this.getCachedAuction(auctionId); return { @@ -111,13 +124,27 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER adUnits: [], }; }, + /** + * @param {Bid} bid + * @param {BIDDER_STATUS} status + */ serializeBidResponse(bid, status) { return { bidder: bid.bidder, isTimeout: (status === BIDDER_STATUS.TIMEOUT), hasBid: (status === BIDDER_STATUS.BID), + params: (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {}, + ...(status === BIDDER_STATUS.BID ? { + cpm: bid.cpm, + currency: bid.currency + } : {}), }; }, + /** + * @param {*} message Greenbids API payload + * @param {Bid} bid Bid to add to the payload + * @param {BIDDER_STATUS} status Bidding status + */ addBidResponseToMessage(message, bid, status) { const adUnitCode = bid.adUnitCode.toLowerCase(); const adUnitIndex = message.adUnits.findIndex((adUnit) => { @@ -133,8 +160,11 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER if (bidderIndex === -1) { message.adUnits[adUnitIndex].bidders.push(this.serializeBidResponse(bid, status)); } else { + message.adUnits[adUnitIndex].bidders[bidderIndex].params = (bid.params && Object.keys(bid.params).length > 0) ? bid.params : {}; if (status === BIDDER_STATUS.BID) { message.adUnits[adUnitIndex].bidders[bidderIndex].hasBid = true; + message.adUnits[adUnitIndex].bidders[bidderIndex].cpm = bid.cpm; + message.adUnits[adUnitIndex].bidders[bidderIndex].currency = bid.currency; } else if (status === BIDDER_STATUS.TIMEOUT) { message.adUnits[adUnitIndex].bidders[bidderIndex].isTimeout = true; } @@ -193,9 +223,12 @@ export const greenbidsAnalyticsAdapter = Object.assign(adapter({ANALYTICS_SERVER }, handleAuctionEnd(auctionEndArgs) { const cachedAuction = this.getCachedAuction(auctionEndArgs.auctionId); - this.sendEventMessage('/', - this.createBidMessage(auctionEndArgs, cachedAuction) - ); + const isFilteringForced = getParameterByName('greenbids_force_filtering'); + if (!isFilteringForced) { + this.sendEventMessage('/', + this.createBidMessage(auctionEndArgs, cachedAuction) + ) + }; }, handleBidTimeout(timeoutBids) { timeoutBids.forEach((bid) => { diff --git a/modules/greenbidsRtdProvider.js b/modules/greenbidsRtdProvider.js index 5496fc71c4e..e350cebb33e 100644 --- a/modules/greenbidsRtdProvider.js +++ b/modules/greenbidsRtdProvider.js @@ -1,11 +1,11 @@ -import { logError, deepClone, generateUUID, deepSetValue, deepAccess } from '../src/utils.js'; +import { logError, logInfo, logWarn, logMessage, deepClone, generateUUID, deepSetValue, deepAccess, getParameterByName } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; const MODULE_NAME = 'greenbidsRtdProvider'; -const MODULE_VERSION = '2.0.0'; +const MODULE_VERSION = '2.0.1'; const ENDPOINT = 'https://t.greenbids.ai'; const rtdOptions = {}; @@ -46,6 +46,7 @@ function getBidRequestData(reqBidsConfigObj, callback, config, userConsent) { function createPromise(reqBidsConfigObj, greenbidsId) { return new Promise((resolve) => { const timeoutId = setTimeout(() => { + logWarn('GreenbidsRtdProvider: Greenbids API timeout, skipping shaping'); resolve(reqBidsConfigObj); }, rtdOptions.timeout); ajax( @@ -57,6 +58,7 @@ function createPromise(reqBidsConfigObj, greenbidsId) { }, error: () => { clearTimeout(timeoutId); + logWarn('GreenbidsRtdProvider: Greenbids API response error, skipping shaping'); resolve(reqBidsConfigObj); }, }, @@ -73,11 +75,17 @@ function createPromise(reqBidsConfigObj, greenbidsId) { function processSuccessResponse(response, timeoutId, reqBidsConfigObj, greenbidsId) { clearTimeout(timeoutId); - const responseAdUnits = JSON.parse(response); - updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits, greenbidsId); + try { + const responseAdUnits = JSON.parse(response); + updateAdUnitsBasedOnResponse(reqBidsConfigObj.adUnits, responseAdUnits, greenbidsId); + } catch (e) { + logWarn('GreenbidsRtdProvider: Greenbids API response parsing error, skipping shaping'); + } } function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { + const isFilteringForced = getParameterByName('greenbids_force_filtering'); + const isFilteringDisabled = getParameterByName('greenbids_disable_filtering'); adUnits.forEach((adUnit) => { const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); if (matchingAdUnit) { @@ -86,7 +94,12 @@ function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { keptInAuction: matchingAdUnit.bidders, isExploration: matchingAdUnit.isExploration }); - if (!matchingAdUnit.isExploration) { + if (matchingAdUnit.isExploration || isFilteringDisabled) { + logMessage('Greenbids Rtd: either exploration traffic, or disabled filtering flag detected'); + } else if (isFilteringForced) { + adUnit.bids = []; + logInfo('Greenbids Rtd: filtering flag detected, forcing filtering of Rtd module.'); + } else { removeFalseBidders(adUnit, matchingAdUnit); } } diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index f7db6d878f1..4f3dfb94747 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -7,7 +7,8 @@ import { mergeDeep, logWarn, isNumber, - isStr + isStr, + isPlainObject } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -15,6 +16,7 @@ import { Renderer } from '../src/Renderer.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; +import { getBidFromResponse } from '../libraries/processResponse/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -274,6 +276,11 @@ export const spec = { userExt.device = { ...ortb2UserExtDevice }; } + // if present, add device data object from ortb2 to the request + if (bidderRequest?.ortb2?.device) { + request.device = bidderRequest.ortb2.device; + } + if (userIdAsEids && userIdAsEids.length) { userExt = userExt || {}; userExt.eids = [...userIdAsEids]; @@ -441,7 +448,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidRequest, bidResponses, RendererConst, bidderCode); + _addBidResponse(getBidFromResponse(respItem, LOG_ERROR_MESS), bidRequest, bidResponses, RendererConst, bidderCode); }); } if (errorMessage) logError(errorMessage); @@ -502,7 +509,7 @@ function _getFloor (mediaTypes, bid) { size: bid.sizes.map(([w, h]) => ({w, h})) }); - if (typeof floorInfo === 'object' && + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = Math.max(floor, parseFloat(floorInfo.floor)); @@ -512,17 +519,6 @@ function _getFloor (mediaTypes, bid) { return floor; } -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - function _addBidResponse(serverBid, bidRequest, bidResponses, RendererConst, bidderCode) { if (!serverBid) return; let errorMessage; diff --git a/modules/growthCodeAnalyticsAdapter.js b/modules/growthCodeAnalyticsAdapter.js index d2cd160a364..0b1f343e4dc 100644 --- a/modules/growthCodeAnalyticsAdapter.js +++ b/modules/growthCodeAnalyticsAdapter.js @@ -31,7 +31,7 @@ let analyticsType = 'endpoint'; let growthCodeAnalyticsAdapter = Object.assign(adapter({url: url, analyticsType}), { track({eventType, args}) { - let eventData = args ? JSON.parse(JSON.stringify(args)) : {}; + let eventData = args ? utils.deepClone(args) : {}; let data = {}; if (!trackEvents.includes(eventType)) return; switch (eventType) { @@ -140,7 +140,7 @@ function logToServer() { if (pid === DEFAULT_PID) return; if (eventQueue.length >= 1) { // Get the correct GCID - let gcid = localStorage.getItem('gcid') + let gcid = storage.getDataFromLocalStorage('gcid'); let data = { session: sessionId, diff --git a/modules/growthCodeRtdProvider.js b/modules/growthCodeRtdProvider.js index b12b25a0951..a8893b9648e 100644 --- a/modules/growthCodeRtdProvider.js +++ b/modules/growthCodeRtdProvider.js @@ -75,7 +75,7 @@ function callServer(configParams, items, expiresAt, userConsent) { storage.removeDataFromLocalStorage(RTD_EXPIRE_KEY, null) } if ((items === null) && (isNaN(expiresAt))) { - let gcid = localStorage.getItem('gcid') + let gcid = storage.getDataFromLocalStorage('gcid') let url = configParams.url ? configParams.url : ENDPOINT_URL; url = tryAppendQueryString(url, 'pid', configParams.pid); diff --git a/modules/gumgumBidAdapter.js b/modules/gumgumBidAdapter.js index 4b12431302f..2911522c89b 100644 --- a/modules/gumgumBidAdapter.js +++ b/modules/gumgumBidAdapter.js @@ -29,13 +29,14 @@ let invalidRequestIds = {}; let pageViewId = null; // TODO: potential 0 values for browserParams sent to ad server -function _getBrowserParams(topWindowUrl) { +function _getBrowserParams(topWindowUrl, mosttopLocation) { const paramRegex = paramName => new RegExp(`[?#&](${paramName}=(.*?))($|&)`, 'i'); let browserParams = {}; let topWindow; let topScreen; let topUrl; + let mosttopURL let ggad; let ggdeal; let ns; @@ -74,6 +75,7 @@ function _getBrowserParams(topWindowUrl) { topWindow = global.top; topScreen = topWindow.screen; topUrl = topWindowUrl || ''; + mosttopURL = mosttopLocation || ''; } catch (error) { logError(error); return browserParams; @@ -85,6 +87,7 @@ function _getBrowserParams(topWindowUrl) { sw: topScreen.width, sh: topScreen.height, pu: stripGGParams(topUrl), + tpl: mosttopURL, ce: storage.cookiesAreEnabled(), dpr: topWindow.devicePixelRatio || 1, jcsi: JSON.stringify(JCSI), @@ -215,11 +218,12 @@ function _getVidParams(attributes) { } /** - * Gets bidfloor - * @param {Object} mediaTypes - * @param {Number} bidfloor - * @param {Object} bid - * @returns {Number} floor + * Retrieves the bid floor value, which is the minimum acceptable bid for an ad unit. + * This function calculates the bid floor based on the given media types and other bidding parameters. + * @param {Object} mediaTypes - The media types specified for the bid, which might influence floor calculations. + * @param {number} staticBidFloor - The default or static bid floor set for the bid. + * @param {Object} bid - The bid object which may contain a method to get dynamic floor values. + * @returns {Object} An object containing the calculated bid floor and its currency. */ function _getFloor(mediaTypes, staticBidFloor, bid) { const curMediaType = Object.keys(mediaTypes)[0] || 'banner'; @@ -229,7 +233,7 @@ function _getFloor(mediaTypes, staticBidFloor, bid) { const { currency, floor } = bid.getFloor({ mediaType: curMediaType, size: '*' - }); + }) || {}; floor && (bidFloor.floor = floor); currency && (bidFloor.currency = currency); @@ -243,6 +247,41 @@ function _getFloor(mediaTypes, staticBidFloor, bid) { return bidFloor; } +/** + * Retrieves the device data from the ORTB2 object + * @param {Object} ortb2Data ORTB2 object + * @returns {Object} Device data + */ +function _getDeviceData(ortb2Data) { + const _device = deepAccess(ortb2Data, 'device') || {}; + + // set device data params from ortb2 + const _deviceRequestParams = { + ip: _device.ip, + ipv6: _device.ipv6, + ua: _device.ua, + dnt: _device.dnt, + os: _device.os, + osv: _device.osv, + dt: _device.devicetype, + lang: _device.language, + make: _device.make, + model: _device.model, + ppi: _device.ppi, + pxratio: _device.pxratio, + foddid: _device?.ext?.fiftyonedegrees_deviceId, + }; + + // return device data params with only non-empty values + return Object.keys(_deviceRequestParams) + .reduce((r, key) => { + if (_deviceRequestParams[key] !== undefined) { + r[key] = _deviceRequestParams[key]; + } + return r; + }, {}); +} + /** * loops through bannerSizes array to get greatest slot dimensions * @param {number[][]} sizes @@ -290,10 +329,10 @@ function getEids(userId) { } /** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequests[]} - an array of bids - * @return ServerRequest Info describing the request to the server. + * Builds requests for bids. + * @param {validBidRequests[]} validBidRequests - An array of valid bid requests. + * @param {Object} bidderRequest - The bidder's request information. + * @returns {Object[]} An array of server requests. */ function buildRequests(validBidRequests, bidderRequest) { const bids = []; @@ -303,6 +342,7 @@ function buildRequests(validBidRequests, bidderRequest) { const timeout = bidderRequest && bidderRequest.timeout const coppa = config.getConfig('coppa') === true ? 1 : 0; const topWindowUrl = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page; + const mosttopLocation = bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.topmostLocation _each(validBidRequests, bidRequest => { const { bidId, @@ -315,7 +355,8 @@ function buildRequests(validBidRequests, bidderRequest) { } = bidRequest; const { currency, floor } = _getFloor(mediaTypes, params.bidfloor, bidRequest); const eids = getEids(userId); - const gpid = deepAccess(ortb2Imp, 'ext.data.pbadslot') || deepAccess(ortb2Imp, 'ext.data.adserver.adslot'); + const gpid = deepAccess(ortb2Imp, 'ext.gpid') || deepAccess(ortb2Imp, 'ext.data.pbadslot'); + const paapiEligible = deepAccess(ortb2Imp, 'ext.ae') === 1 let sizes = [1, 1]; let data = {}; data.displaymanager = 'Prebid.js - gumgum'; @@ -373,15 +414,12 @@ function buildRequests(validBidRequests, bidderRequest) { data.fp = floor; data.fpc = currency; } - + if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.site) { + setIrisId(data, bidderRequest.ortb2.site, params); + } if (params.iriscat && typeof params.iriscat === 'string') { data.iriscat = params.iriscat; } - - if (params.irisid && typeof params.irisid === 'string') { - data.irisid = params.irisid; - } - if (params.zone || params.pubId) { params.zone ? (data.t = params.zone) : (data.pubId = params.pubId); @@ -405,7 +443,9 @@ function buildRequests(validBidRequests, bidderRequest) { } else { // legacy params data = { ...data, ...handleLegacyParams(params, sizes) }; } - + if (paapiEligible) { + data.ae = paapiEligible + } if (gdprConsent) { data.gdprApplies = gdprConsent.gdprApplies ? 1 : 0; } @@ -432,6 +472,11 @@ function buildRequests(validBidRequests, bidderRequest) { if (schain && schain.nodes) { data.schain = _serializeSupplyChainObj(schain); } + Object.assign( + data, + _getBrowserParams(topWindowUrl, mosttopLocation), + _getDeviceData(bidderRequest?.ortb2), + ); bids.push({ id: bidId, @@ -442,11 +487,32 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl)) + data }); }); return bids; } +export function getCids(site) { + if (site.content && Array.isArray(site.content.data)) { + for (const dataItem of site.content.data) { + if (dataItem.name.includes('iris.com') || dataItem.name.includes('iris.tv')) { + return dataItem.ext.cids.join(','); + } + } + } + return null; +} +export function setIrisId(data, site, params) { + let irisID = getCids(site); + if (irisID) { + data.irisid = irisID; + } else { + // Just adding this chechk for safty and if needed we can remove + if (params.irisid && typeof params.irisid === 'string') { + data.irisid = params.irisid; + } + } +} function handleLegacyParams(params, sizes) { const data = {}; diff --git a/modules/hadronAnalyticsAdapter.js b/modules/hadronAnalyticsAdapter.js index 7394deed9e1..95a1dfaa5e2 100644 --- a/modules/hadronAnalyticsAdapter.js +++ b/modules/hadronAnalyticsAdapter.js @@ -53,8 +53,7 @@ let analyticsType = 'endpoint'; let hadronAnalyticsAdapter = Object.assign(adapter({url: HADRON_ANALYTICS_URL, analyticsType}), { track({eventType, args}) { - args = utils.cloneEventArguments(args || {}); - + args = args ? utils.deepClone(args) : {}; var data = {}; if (!eventsToTrack.includes(eventType)) return; switch (eventType) { diff --git a/modules/hadronIdSystem.js b/modules/hadronIdSystem.js index bdb8e634de6..ccd63bc0184 100644 --- a/modules/hadronIdSystem.js +++ b/modules/hadronIdSystem.js @@ -19,9 +19,9 @@ import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterM * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse */ -const LOG_PREFIX = '[hadronIdSystem]'; -const HADRONID_LOCAL_NAME = 'auHadronId'; -const MODULE_NAME = 'hadronId'; +export const MODULE_NAME = 'hadronId'; +const LOG_PREFIX = `[${MODULE_NAME}System]`; +export const LS_TAM_KEY = 'auHadronId'; const AU_GVLID = 561; const DEFAULT_HADRON_URL_ENDPOINT = 'https://id.hadron.ad.gt/api/v1/pbhid'; @@ -68,11 +68,9 @@ export const hadronIdSubmodule = { * @returns {Object} */ decode(value) { - const hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); - if (isStr(hadronId)) { - return {hadronId: hadronId}; + return { + hadronId: isStr(value) ? value : value.hasOwnProperty('id') ? value.id[MODULE_NAME] : value[MODULE_NAME] } - return (value && typeof value['hadronId'] === 'string') ? {'hadronId': value['hadronId']} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -81,14 +79,19 @@ export const hadronIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { + logInfo(LOG_PREFIX, `getId is called`, config); if (!isPlainObject(config.params)) { config.params = {}; } - const partnerId = config.params.partnerId | 0; - let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); - if (isStr(hadronId)) { - return {id: {hadronId}}; + let hadronId = ''; + // at this point hadronId was not found by prebid, let check if it is in the webpage by other ways + hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY); + if (isStr(hadronId) && hadronId.length > 0) { + logInfo(LOG_PREFIX, `${LS_TAM_KEY} found in localStorage = ${hadronId}`) + // return {callback: function(cb) { cb(hadronId) }}; + return {id: hadronId} } + const partnerId = config.params.partnerId | 0; const resp = function (callback) { let responseObj = {}; const callbacks = { @@ -98,11 +101,13 @@ export const hadronIdSubmodule = { responseObj = JSON.parse(response); } catch (error) { logError(error); + callback(); } logInfo(LOG_PREFIX, `Response from backend is ${response}`, responseObj); - hadronId = responseObj['hadronId']; - storage.setDataInLocalStorage(HADRONID_LOCAL_NAME, hadronId); - responseObj = {id: {hadronId}}; + if (isPlainObject(responseObj) && responseObj.hasOwnProperty(MODULE_NAME)) { + hadronId = responseObj[MODULE_NAME]; + } + responseObj = hadronId; // {id: {hadronId: hadronId}}; } callback(responseObj); }, @@ -137,7 +142,7 @@ export const hadronIdSubmodule = { url += `${gppConsent.applicableSections ? '&gpp_sid=' + encodeURIComponent(gppConsent.applicableSections) : ''}`; } - logInfo(LOG_PREFIX, `hadronId not found in storage, calling home (${url})`); + logInfo(LOG_PREFIX, `${MODULE_NAME} not found, calling home (${url})`); ajax(url, callbacks, undefined, {method: 'GET'}); }; diff --git a/modules/hadronIdSystem.md b/modules/hadronIdSystem.md index 212030cbcd9..f58cd46ef61 100644 --- a/modules/hadronIdSystem.md +++ b/modules/hadronIdSystem.md @@ -12,7 +12,7 @@ pbjs.setConfig({ userIds: [{ name: 'hadronId', params: { - partnerId: 1234 // change it to the Partner ID you'll get from Audigent + partnerId: 1234 // change it to the Partner ID you got from Audigent }, storage: { name: 'hadronId', @@ -25,14 +25,13 @@ pbjs.setConfig({ ## Parameter Descriptions for the `usersync` Configuration Section The below parameters apply only to the HadronID User ID Module integration. -| Param under usersync.userIds[] | Scope | Type | Description | Example | -|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------| -| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | -| 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. | `"hadronid"` | -| 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 Hadron 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 | `{"hadronId": "eb33b0cb-8d35-4722-b9c0-1a31d4064888"}` | +| Param under usersync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|---------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------| +| name | Required | String | ID value for the HadronID module - `"hadronId"` | `"hadronId"` | +| 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 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. The recommended value is `hadronId`. | `"auHadronId"` | +| storage.expires | Optional | Integer | How long (in days) the user ID information will be stored. The recommended value is 14 days. | `14` | +| value | Optional | Object | Used only if the page has a separate mechanism for storing the Hadron 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 | `{"hadronId": "0aRSTUAackg79ijgd8e8j6kah9ed9j6hdfgb6cl00volopxo00npzjmmb"}` | | params | Optional | Object | Used to store params for the id system | -| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | - | +| params.partnerId | Required | Number | This is the Audigent Partner ID obtained from Audigent. | `1234` | diff --git a/modules/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 5c604709b4b..eae85db3c34 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -5,12 +5,11 @@ * @module modules/hadronRtdProvider * @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, logInfo} from '../src/utils.js'; +import {isFn, isStr, isArray, deepEqual, isPlainObject, logInfo} from '../src/utils.js'; import {loadExternalScript} from '../src/adloader.js'; import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; @@ -18,14 +17,13 @@ import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule */ -const LOG_PREFIX = 'User ID - HadronRtdProvider submodule: '; +const LOG_PREFIX = '[HadronRtdProvider] '; const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'hadron'; const AU_GVLID = 561; -const HADRON_ID_DEFAULT_URL = 'https://id.hadron.ad.gt/api/v1/hadronid?_it=prebid'; -const HADRON_SEGMENT_URL = 'https://id.hadron.ad.gt/api/v1/rtd'; -export const HALOID_LOCAL_NAME = 'auHadronId'; -export const RTD_LOCAL_NAME = 'auHadronRtd'; +const HADRON_JS_URL = 'https://cdn.hadronid.net/hadron.js'; +const LS_TAM_KEY = 'auHadronId'; +const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); /** @@ -37,19 +35,6 @@ const urlAddParams = (url, params) => { return url + (url.indexOf('?') > -1 ? '&' : '?') + params }; -/** - * 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 @@ -62,11 +47,11 @@ function mergeDeep(target, ...sources) { if (isPlainObject(target) && isPlainObject(source)) { for (const key in source) { if (isPlainObject(source[key])) { - if (!target[key]) Object.assign(target, { [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] }); + Object.assign(target, {[key]: source[key]}); } else if (isArray(target[key])) { source[key].forEach(obj => { let e = 1; @@ -82,7 +67,7 @@ function mergeDeep(target, ...sources) { }); } } else { - Object.assign(target, { [key]: source[key] }); + Object.assign(target, {[key]: source[key]}); } } } @@ -132,7 +117,6 @@ export function addRealTimeData(bidConfig, rtd, rtdConfig) { if (rtdConfig.params && rtdConfig.params.handleRtd) { rtdConfig.params.handleRtd(bidConfig, rtd, rtdConfig, config); } else { - // TODO: this and haloRtdProvider are a copy-paste of each other if (isPlainObject(rtd.ortb2)) { mergeLazy(bidConfig.ortb2Fragments?.global, rtd.ortb2); } @@ -151,6 +135,17 @@ export function addRealTimeData(bidConfig, rtd, rtdConfig) { * @param {Object} userConsent */ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + if (!storage.getDataFromLocalStorage(LS_TAM_KEY)) { + const partnerId = rtdConfig.params.partnerId | 0; + const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl; + const scriptUrl = urlAddParams( + paramOrDefault(hadronIdUrl, HADRON_JS_URL, {}), + `partner_id=${partnerId}&_it=prebid` + ); + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, () => { + logInfo(LOG_PREFIX, 'hadronId JS snippet loaded', scriptUrl); + }) + } if (rtdConfig && isPlainObject(rtdConfig.params) && rtdConfig.params.segmentCache) { let jsonData = storage.getDataFromLocalStorage(RTD_LOCAL_NAME); @@ -165,83 +160,21 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { } } - const userIds = typeof getGlobal().getUserIds === 'function' ? (getGlobal()).getUserIds() : {}; + const userIds = {}; - let hadronId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); - if (isStr(hadronId)) { - if (typeof getGlobal().refreshUserIds === 'function') { - (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); - } - userIds.hadronId = hadronId; - getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); + const allUserIds = getGlobal().getUserIds(); + if (allUserIds.hasOwnProperty('hadronId')) { + userIds['hadronId'] = allUserIds.hadronId; + logInfo(LOG_PREFIX, 'hadronId user module found', allUserIds.hadronId); } else { - window.pubHadronCb = (hadronId) => { - userIds.hadronId = hadronId; - getRealTimeDataAsync(bidConfig, onDone, rtdConfig, userConsent, userIds); + let hadronId = storage.getDataFromLocalStorage(LS_TAM_KEY); + if (isStr(hadronId) && hadronId.length > 0) { + userIds['hadronId'] = hadronId; + logInfo(LOG_PREFIX, 'hadronId TAM found', hadronId); } - const partnerId = rtdConfig.params.partnerId | 0; - const hadronIdUrl = rtdConfig.params && rtdConfig.params.hadronIdUrl; - const scriptUrl = urlAddParams( - paramOrDefault(hadronIdUrl, HADRON_ID_DEFAULT_URL, userIds), - `partner_id=${partnerId}&_it=prebid` - ); - loadExternalScript(scriptUrl, 'hadron', () => { - logInfo(LOG_PREFIX, 'hadronIdTag loaded', scriptUrl); - }) } } -/** - * Async rtd retrieval from Audigent - * @param {Object} bidConfig - * @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', bidConfig.ortb2Fragments.global); - reqParams = rtdConfig.params.requestParams; - } - - if (isPlainObject(window.pubHadronPm)) { - reqParams.pubHadronPm = window.pubHadronPm; - } - - ajax(HADRON_SEGMENT_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 diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index f046c860562..90bc0c78212 100644 --- a/modules/holidBidAdapter.js +++ b/modules/holidBidAdapter.js @@ -5,8 +5,6 @@ import { logMessage, triggerPixel, } from '../src/utils.js'; -import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; import {BANNER} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; @@ -19,8 +17,6 @@ const TIME_TO_LIVE = 300 const TMAX = 500 let wurlMap = {} -events.on(EVENTS.BID_WON, bidWonHandler) - export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -97,13 +93,13 @@ export const spec = { } const bidders = getBidders(serverResponse) - - if (optionsType.iframeEnabled && bidders) { + // note this only does the iframe sync when gdpr consent object exists to match previous behavior (generate error on gdprconsent not existing) + if (optionsType.iframeEnabled && bidders && gdprConsent) { const queryParams = [] queryParams.push('bidders=' + bidders) - queryParams.push('gdpr=' + +gdprConsent.gdprApplies) - queryParams.push('gdpr_consent=' + gdprConsent.consentString) + queryParams.push('gdpr=' + +gdprConsent?.gdprApplies) + queryParams.push('gdpr_consent=' + gdprConsent?.consentString) queryParams.push('usp_consent=' + (uspConsent || '')) let strQueryParams = queryParams.join('&') @@ -120,6 +116,15 @@ export const spec = { return syncs }, + + onBidWon(bid) { + const wurl = getWurl(bid.requestId) + if (wurl) { + logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) + triggerPixel(wurl) + removeWurl(bid.requestId) + } + } } function getImp(bid) { @@ -176,13 +181,4 @@ function getWurl(requestId) { } } -function bidWonHandler(bid) { - const wurl = getWurl(bid.requestId) - if (wurl) { - logMessage(`Invoking image pixel for wurl on BID_WIN: "${wurl}"`) - triggerPixel(wurl) - removeWurl(bid.requestId) - } -} - registerBidder(spec) diff --git a/modules/humansecurityRtdProvider.js b/modules/humansecurityRtdProvider.js new file mode 100644 index 00000000000..aeb872beb8d --- /dev/null +++ b/modules/humansecurityRtdProvider.js @@ -0,0 +1,180 @@ +/** + * This module adds humansecurity provider to the real time data module + * + * The {@link module:modules/realTimeData} module is required + * The module will inject the HUMAN Security script into the context where Prebid.js is initialized, enriching bid requests with specific data to provide advanced protection against ad fraud and spoofing. + * @module modules/humansecurityRtdProvider + * @requires module:modules/realTimeData + */ + +import { submodule } from '../src/hook.js'; +import { + prefixLog, + mergeDeep, + generateUUID, + getWindowSelf, +} from '../src/utils.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + * @typedef {import('../modules/rtdModule/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/rtdModule/index.js').UserConsentData} UserConsentData + */ + +const SUBMODULE_NAME = 'humansecurity'; +const SCRIPT_URL = 'https://sonar.script.ac/prebid/rtd.js'; + +const { logInfo, logWarn, logError } = prefixLog(`[${SUBMODULE_NAME}]:`); + +/** @type {string} */ +let clientId = ''; + +/** @type {boolean} */ +let verbose = false; + +/** @type {string} */ +let sessionId = ''; + +/** @type {Object} */ +let hmnsData = { }; + +/** + * Submodule registration + */ +function main() { + submodule('realTimeData', /** @type {RtdSubmodule} */ ({ + name: SUBMODULE_NAME, + + // + init: (config, userConsent) => { + try { + load(config); + return true; + } catch (err) { + logError('init', err.message); + return false; + } + }, + + getBidRequestData: onGetBidRequestData + })); +} + +/** + * Injects HUMAN Security script on the page to facilitate pre-bid signal collection. + * @param {SubmoduleConfig} config + */ +function load(config) { + // By default, this submodule loads the generic implementation script + // only identified by the referrer information. In the future, if publishers + // want to have analytics where their websites are grouped, they can request + // Client ID from HUMAN, pass it here, and it will enable advanced reporting + clientId = config?.params?.clientId || ''; + if (clientId && (typeof clientId !== 'string' || !/^\w{3,16}$/.test(clientId))) { + throw new Error(`The 'clientId' parameter must be a short alphanumeric string`); + } + + // Load/reset the state + verbose = !!config?.params?.verbose; + sessionId = generateUUID(); + hmnsData = {}; + + // We rely on prebid implementation to get the best domain possible here + // In some cases, it still might be null, though + const refDomain = getRefererInfo().domain || ''; + + // Once loaded, the implementation script will publish an API using + // the session ID value it was given in data attributes + const scriptAttrs = { 'data-sid': sessionId }; + const scriptUrl = `${SCRIPT_URL}?r=${refDomain}${clientId ? `&c=${clientId}` : ''}`; + + loadExternalScript(scriptUrl, MODULE_TYPE_RTD, SUBMODULE_NAME, onImplLoaded, null, scriptAttrs); +} + +/** + * The callback to loadExternalScript + * Establishes the bridge between this RTD submodule and the loaded implementation + */ +function onImplLoaded() { + // We then get a hold on this script using the knowledge of this session ID + const wnd = getWindowSelf(); + const impl = wnd[`sonar_${sessionId}`]; + if (typeof impl !== 'object' || typeof impl.connect !== 'function') { + verbose && logWarn('onload', 'Unable to access the implementation script'); + return; + } + + // And set up a bridge between the RTD submodule and the implementation. + // The first argument is used to identify the caller. + // The callback might be called multiple times to update the signals + // once more precise information is available. + impl.connect(getGlobal(), onImplMessage); +} + +/** + * The bridge function will be called by the implementation script + * to update the token information or report errors + * @param {Object} msg + */ +function onImplMessage(msg) { + if (typeof msg !== 'object') { + return; + } + + switch (msg.type) { + case 'hmns': { + hmnsData = mergeDeep({}, msg.data || {}); + break; + } + case 'error': { + logError('impl', msg.data || ''); + break; + } + case 'warn': { + verbose && logWarn('impl', msg.data || ''); + break; + } + case 'info': { + verbose && logInfo('impl', msg.data || ''); + break; + } + } +} + +/** + * onGetBidRequestData is called once per auction. + * Update the `ortb2Fragments` object with the data from the injected script. + * + * @param {Object} reqBidsConfigObj + * @param {function} callback + * @param {SubmoduleConfig} config + * @param {UserConsentData} userConsent + */ +function onGetBidRequestData(reqBidsConfigObj, callback, config, userConsent) { + // Add device.ext.hmns to the global ORTB data for all vendors to use + // At the time of writing this submodule, "hmns" is an object defined + // internally by humansecurity, and it currently contains "v1" field + // with a token that contains collected signals about this session. + mergeDeep(reqBidsConfigObj.ortb2Fragments.global, { device: { ext: { hmns: hmnsData } } }); + callback(); +} + +/** + * Exporting local (and otherwise encapsulated to this module) functions + * for testing purposes + */ +export const __TEST__ = { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData +}; + +main(); diff --git a/modules/humansecurityRtdProvider.md b/modules/humansecurityRtdProvider.md new file mode 100644 index 00000000000..6722319cbb5 --- /dev/null +++ b/modules/humansecurityRtdProvider.md @@ -0,0 +1,223 @@ +# Overview + +``` +Module Name: HUMAN Security Rtd provider +Module Type: Rtd Provider +Maintainer: alexey@humansecurity.com +``` + +## What is it? + +The HUMAN Security RTD submodule offers publishers a mechanism to integrate pre-bid signal collection +for the purpose of providing real-time protection against all sorts of invalid traffic, +such as bot-generated ad interactions or sophisticated ad fraud schemes. + +## How does it work? + +HUMAN Security RTD submodule generates a HUMAN Security token, which then can be consumed by adapters, +sent within bid requests, and used for bot detection on the backend. + +## Key Facts about the HUMAN Security RTD Submodule + +* Enriches bid requests with IVT signal, historically done post-bid +* No incremental signals collected beyond existing HUMAN post-bid solution +* Offsets negative impact from loss of granularity in IP and User Agent at bid time +* Does not expose collected IVT signal to any party who doesn’t otherwise already have access to the same signal collected post-bid +* Does not introduce meaningful latency, as demonstrated in the Latency section +* Comes at no additional cost to collect IVT signal and make it available at bid time +* Leveraged to differentiate the invalid bid requests at device level, and cannot be used to identify a user or a device, thus preserving privacy. + +# Build + +First, make sure to add the HUMAN Security submodule to your Prebid.js package with: + +```bash +gulp build --modules="rtdModule,humansecurityRtdProvider,..." +``` + +> `rtdModule` is a required module to use HUMAN Security RTD module. + +# Configuration + +This module is configured as part of the `realTimeData.dataProviders` object. +Please refer to [Prebid Documentation](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#setConfig-realTimeData) +on RTD module configuration for details on required and optional parameters of `realTimeData`. + +By default, using this submodule *does not require any prior communication with HUMAN, nor any special configuration*, +besides just indicating that it should be loaded: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'humansecurity' + }] + } +}); +``` + +It can be optionally parameterized, for example, to include client ID obtained from HUMAN, +should any advanced reporting be needed, or to have verbose output for troubleshooting: + +```javascript +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'humansecurity', + params: { + clientId: 'ABC123', + verbose: true + } + }] + } +}); +``` + +## Supported parameters + +| Name |Type | Description | Required | +| :--------------- | :------------ | :------------------------------------------------------------------ |:---------| +| `clientId` | String | Should you need advanced reporting, contact [prebid@humansecurity.com](prebid@humansecurity.com) to receive client ID. | No | +| `verbose` | Boolean | Only set to `true` if troubleshooting issues. | No | + +## Logging, latency and troubleshooting + +The optional `verbose` parameter can be especially helpful to troubleshoot any issues and/or monitor latency. + +By default, the submodule may, in case of unexpected issues, invoke `logError`, emitting `auctionDebug` events +of type `ERROR`. With `verbose` parameter set to `true`, it may additionally: + +* Call `logWarning`, resulting in `auctionDebug` events of type `WARNING`, +* Call `logInfo` with latency information. + * To observe these messages in console, Prebid.js must be run in + [debug mode](https://docs.prebid.org/dev-docs/publisher-api-reference/setConfig.html#debugging) - + either by adding `?pbjs_debug=true` to your page's URL, or by configuring with `pbjs.setConfig({ debug: true });` + +Example output of the latency information: + +``` +INFO: [humansecurity]: impl JS time to init (ms): 6. +INFO: [humansecurity]: impl JS time to collect (ms): 13. +``` + +Here, the two reported metrics are how much time the signal collection script spent blocking on initialization, +and the total time required to obtain the signals, respectively. Note that "time to collect" metric accounts +for all the time spent since the script has started initializing until the signals were made available to the bidders, +therefore it includes "time to init", and typically some non-blocking time spent waiting for signals. Only “time to init” is blocking. + +# How can I contribute? + +Prebid has launched a Measurement Taskforce to address signal deprecation and measurement in the current environment, +which has become a publisher-level issue. Without a solution, granularity of measurement disappears. +If you would like to participate to help identify and develop solutions to these problems such as the one tackled +by this submodule, please consider joining the [Measurement Taskforce](https://prebid.org/project-management-committees/). + +# Notes + +## Operation model + +Following is the expected data flow: + +* Prebid.js gets initialized, including the HUMAN RTD submodule. +* The submodule loads the signal collection implementation script from a high-performance, low latency endpoint. +* This script starts collecting the signals, and makes them available to the RTD submodule as soon as possible. +* The RTD submodule places the collected signals into the ORTB structure for bid adapters to pick up. +* Bid adapters are expected to retrieve the `ortb2.device.ext.hmns` object and incorporate it into their bid requests. +* Bid requests having the `ortb2.device.ext.hmns` data allow their backend to make more informative requests to HUMAN Ad Fraud Defense. + * Should bid requests be passed to other platforms during the bidding process, adapter developers are + encouraged to keep `ortb2.device.ext.hmns` so that, for example, a downstream DSP can also have this data passed to HUMAN. + +## Remarks on the collected signals + +There are a few points that are worth being mentioned separately, to avoid confusion and unnecessary suspicion: + +* The nature of the collected signals is exactly the same as those already collected in analytics scripts + that arrive in the ads via existing post-bid processes. +* The signals themselves are even less verbose than those HUMAN normally collects post-bid, because of timing / performance requirements. +* No signals attempt to identify users. Their only purpose is to classify traffic into valid / invalid. +* The signal collection script is external to Prebid.js. This ensures that it can be constantly kept up to date with + the ever-evolving nature of the threat landscape without the publishers having to rebuild their Prebid.js frequently. + * The signal collection script is also obfuscated, as a defense-in-depth measure in order to complicate tampering by + bad actors, as are all similar scripts in the industry, which is something that cannot be accommodated by Prebid.js itself. + +## Why is this approach an innovation? + +Historically, IVT protection is achieved via dropping analytics scripts and/or pixels in the ads, which enriches impression data with collected signals. +Those signals, when analyzed by IVT protection vendors, allow distinguishing valid from invalid traffic, but only retroactively - +after the impression was served, and all the participant infrastructures have already participated in serving the request. + +This not only leads to unnecessary infrastructure costs, but to uncomfortable and often difficult processes of reconciliation +and reimbursement, or claw-back. When handled only at the post-bid stage, the true bad actors have already achieved their objectives, +and legitimate advertisers, platforms, and publishers are left holding the bag. + +HUMAN’s Ad Fraud Defense solves this problem by making predictions at the pre-bid stage about whether the traffic is fraudulent, +allowing the platforms to knowingly not participate in the IVT-generated auctions. + +However, the challenge in making those predictions is that even these prebid predictions rely primarily on historical data, +which not only introduces lag, but typically might be less accurate than direct decision making (were it possible) using +the high-quality signals obtained from the pixels and/or JS analytics scripts delivered in the ads. + +The HUMAN Security RTD submodule bridges the gap by introducing a new innovation: it **facilitates the very same signal +collection that is typically performed post-bid, but at the pre-bid stage, and makes the signals available during bidding.** +This not only permits for accurate invalid traffic detection at the earliest stages of the auction process, but diminishes +the impacts of signal deprecation such as the loss of IP and User Agent on effective fraud mitigation. + +## Why is this good for publishers? + +In the process of Invalid Traffic reconciliation, publishers are often the last to know, as they are informed by their downstream +partners that the inventory they had provided in good faith has been detected as invalid traffic. This is most painful when it +happens via post-bid detection when publishers are often the last party in the chain from whom the others can collect clawbacks, +and the publishers themselves are left with little recourse. And when invalid traffic is blocked by platforms prebid, it is after +the fact of publishers having sent out bid requests, thus harming fill rates, revenue opportunities, and overall auction and bidding +efficiencies. And of course, invalid traffic whether detected post-bid or pre-bid is damaging to the publisher’s reputation +with its demand partners. + +The HUMAN Security RTD submodule creates a more efficient integration for the process of invalid traffic mitigation. +Invalid traffic detection and filtration is being done already with or without the participation of publishers, and measurement +will be done on the ad inventory because advertisers need it to manage their own ad spend. The HUMAN Security RTD submodule gives +publishers a direct seat, and in fact the first seat, in the invalid traffic detection process, allowing it to be done effectively, +directly, and in a way that provides the publisher with more direct insight. + +Existing models of signal deprecation suggest that IP protection is going to be 100 times or more less granular. +This would normally be expected to significantly reduce the ability to do prebid publisher-side predictions. This in turn would prevent +the ability to see if specific impressions are bad and instead potentially result in the whole publisher being identified as being +invalid traffic by a buyer. It is important to note that the purpose of data collection by the HUMAN Security RTD submodule is +specifically for invalid traffic detection and filtration. It will not be used for unrelated and unauthorized purposes +like targeting audiences, etc. + +The HUMAN Security RTD submodule makes sure to have the best integration possible to avoid revenue loss. +It will help publishers avoid painful clawbacks. Currently, clawbacks are based on opaque measurement processes downstream from +publishers where the information is controlled and withheld. The HUMAN Security RTD submodule will make publishers a more direct +party to the measurement and verification process and help make sure the origin and the recipient match. + +Importantly, the effective use of the HUMAN Security RTD submodule signifies to SSPs and buyers that the publisher +is a joint partner in ensuring quality ad delivery, and demonstrates that the publisher is a premium supply source. + +Finally, the HUMAN Security RTD submodule sets the ecosystem up for a future where publisher level reporting is facilitated. +This will allow for increased transparency about what is happening with publisher inventory, further enhancing and +ensuring the value of the inventory. + +## FAQ + +### Is latency an issue? + +The HUMAN Security RTD submodule is designed to minimize any latency in the auction within normal SLAs. + +### Do publishers get any insight into how the measurement is judged? + +Having the The HUMAN Security RTD submodule be part of the prebid process will allow the publisher to have insight +into the invalid traffic metrics as they are determined and provide confidence that they are delivering quality +inventory to the buyer. + +### How are privacy concerns addressed? + +The HUMAN Security RTD submodule seeks to reduce the impacts from signal deprecation that are inevitable without +compromising privacy by avoiding re-identification. Each bid request is enriched with just enough signal +to identify if the traffic is invalid or not. + +By having the The HUMAN Security RTD submodule operate at the Prebid level, data can be controlled +and not as freely passed through the bidstream where it may be accessible to various unknown parties. + +Note: anti-fraud use cases typically have carve outs in laws and regulations to permit data collection +essential for effective fraud mitigation, but this does not constitute legal advice and you should +consult your attorney when making data access decisions. diff --git a/modules/id5AnalyticsAdapter.js b/modules/id5AnalyticsAdapter.js index 70da467ff7c..10fe8d82ef4 100644 --- a/modules/id5AnalyticsAdapter.js +++ b/modules/id5AnalyticsAdapter.js @@ -4,7 +4,6 @@ import adapterManager from '../src/adapterManager.js'; import { ajax } from '../src/ajax.js'; import { logInfo, logError } from '../src/utils.js'; import * as events from '../src/events.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const { AUCTION_END, @@ -33,7 +32,7 @@ const FLUSH_EVENTS = [ const CONFIG_URL_PREFIX = 'https://api.id5-sync.com/analytics' const TZ = new Date().getTimezoneOffset(); -const PBJS_VERSION = getGlobal().version; +const PBJS_VERSION = 'v' + '$prebid.version$'; const ID5_REDACTED = '__ID5_REDACTED__'; const isArray = Array.isArray; diff --git a/modules/id5IdSystem.js b/modules/id5IdSystem.js index e7a7ec7f037..65e9be46a56 100644 --- a/modules/id5IdSystem.js +++ b/modules/id5IdSystem.js @@ -13,14 +13,13 @@ import { isPlainObject, logError, logInfo, - logWarn, - safeJSONParse + logWarn } from '../src/utils.js'; import {fetch} from '../src/ajax.js'; import {submodule} from '../src/hook.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {getStorageManager} from '../src/storageManager.js'; -import {uspDataHandler, gppDataHandler} from '../src/adapterManager.js'; +import {gppDataHandler, uspDataHandler} from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; import {GreedyPromise} from '../src/utils/promise.js'; import {loadExternalScript} from '../src/adloader.js'; @@ -34,17 +33,11 @@ import {loadExternalScript} from '../src/adloader.js'; const MODULE_NAME = 'id5Id'; const GVLID = 131; -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: '; const ID5_API_CONFIG_URL = 'https://id5-sync.com/api/config/prebid'; const ID5_DOMAIN = 'id5-sync.com'; - -// 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 TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); @@ -134,12 +127,13 @@ export const id5IdSubmodule = { * @returns {(Object|undefined)} */ decode(value, config) { - let universalUid; + let universalUid, publisherTrueLinkId; let ext = {}; if (value && typeof value.universal_uid === 'string') { universalUid = value.universal_uid; ext = value.ext || ext; + publisherTrueLinkId = value.publisherTrueLinkId; } else { return undefined; } @@ -159,6 +153,12 @@ export const id5IdSubmodule = { }; } + if (publisherTrueLinkId) { + responseObj.trueLinkId = { + uid: publisherTrueLinkId, + }; + } + const abTestingResult = deepAccess(value, 'ab_testing.result'); switch (abTestingResult) { case 'control': @@ -223,7 +223,7 @@ export const id5IdSubmodule = { * @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. + * @return {IdResponse} A response object that contains id. */ extendId(config, consentData, cacheIdObj) { if (!hasWriteConsentToLocalStorage(consentData)) { @@ -231,12 +231,13 @@ export const id5IdSubmodule = { return cacheIdObj; } - const partnerId = validateConfig(config) ? config.params.partner : 0; - incrementNb(partnerId); - logInfo(LOG_PREFIX + 'using cached ID', cacheIdObj); + if (cacheIdObj) { + cacheIdObj.nbPage = incrementNb(cacheIdObj) + } return cacheIdObj; }, + primaryIds: ['id5id', 'trueLinkId'], eids: { 'id5id': { getValue: function (data) { @@ -263,7 +264,22 @@ export const id5IdSubmodule = { return data.ext; } } + }, + 'trueLinkId': { + getValue: function (data) { + return data.uid; + }, + getSource: function (data) { + return TRUE_LINK_SOURCE; + }, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext; + } + } } + } }; @@ -378,8 +394,10 @@ export class IdFetchFlow { const params = this.submoduleConfig.params; const hasGdpr = (this.gdprConsentData && typeof this.gdprConsentData.gdprApplies === 'boolean' && this.gdprConsentData.gdprApplies) ? 1 : 0; const referer = getRefererInfo(); - const signature = (this.cacheIdObj && this.cacheIdObj.signature) ? this.cacheIdObj.signature : getLegacyCookieSignature(); - const nbPage = incrementAndResetNb(params.partner); + const signature = this.cacheIdObj ? this.cacheIdObj.signature : undefined; + const nbPage = incrementNb(this.cacheIdObj); + const trueLinkInfo = window.id5Bootstrap ? window.id5Bootstrap.getTrueLinkInfo() : {booted: false}; + const data = { 'partner': params.partner, 'gdpr': hasGdpr, @@ -392,7 +410,8 @@ export class IdFetchFlow { 'u': referer.stack[0] || window.location.href, 'v': '$prebid.version$', 'storage': this.submoduleConfig.storage, - 'localStorage': storage.localStorageIsEnabled() ? 1 : 0 + 'localStorage': storage.localStorageIsEnabled() ? 1 : 0, + 'true_link': trueLinkInfo }; // pass in optional data, but only if populated @@ -430,7 +449,9 @@ export class IdFetchFlow { #processFetchCallResponse(fetchCallResponse) { try { if (fetchCallResponse.privacy) { - storeInLocalStorage(ID5_PRIVACY_STORAGE_NAME, JSON.stringify(fetchCallResponse.privacy), NB_EXP_DAYS); + if (window.id5Bootstrap && window.id5Bootstrap.setPrivacy) { + window.id5Bootstrap.setPrivacy(fetchCallResponse.privacy); + } } } catch (error) { logError(LOG_PREFIX + 'Error while writing privacy info into local storage.', error); @@ -446,7 +467,7 @@ async function loadExternalModule(url) { resolve(); } else { try { - loadExternalScript(url, 'id5', resolve); + loadExternalScript(url, MODULE_TYPE_UID, 'id5', resolve); } catch (error) { reject(error); } @@ -478,89 +499,19 @@ function validateConfig(config) { 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) { - 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) { - logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'. In a future release this may become a strict requirement`); + logWarn(LOG_PREFIX + `storage name recommended to be '${ID5_STORAGE_NAME}'.`); } return true; } -export function expDaysStr(expDays) { - return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); -} - -export function nbCacheName(partnerId) { - return `${ID5_STORAGE_NAME}_${partnerId}_nb`; -} - -export function storeNbInCache(partnerId, nb) { - storeInLocalStorage(nbCacheName(partnerId), nb, NB_EXP_DAYS); -} - -export function getNbFromCache(partnerId) { - let cacheNb = getFromLocalStorage(nbCacheName(partnerId)); - return (cacheNb) ? parseInt(cacheNb) : 0; -} - -function incrementNb(partnerId) { - const nb = (getNbFromCache(partnerId) + 1); - storeNbInCache(partnerId, nb); - return nb; -} - -function incrementAndResetNb(partnerId) { - const result = incrementNb(partnerId); - storeNbInCache(partnerId, 0); - return result; -} - -function getLegacyCookieSignature() { - let legacyStoredValue; - LEGACY_COOKIE_NAMES.forEach(function (cookie) { - if (storage.getCookie(cookie)) { - legacyStoredValue = safeJSONParse(storage.getCookie(cookie)) || legacyStoredValue; - } - }); - return (legacyStoredValue && legacyStoredValue.signature) || ''; -} - -/** - * 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); - } +function incrementNb(cachedObj) { + if (cachedObj && cachedObj.nbPage !== undefined) { + return cachedObj.nbPage + 1; + } else { + return 1; } - // 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 {number} expDays - */ -export function storeInLocalStorage(key, value, expDays) { - storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); - storage.setDataInLocalStorage(`${key}`, value); } /** diff --git a/modules/id5IdSystem.md b/modules/id5IdSystem.md index 592c69056fa..a2782b70559 100644 --- a/modules/id5IdSystem.md +++ b/modules/id5IdSystem.md @@ -37,7 +37,7 @@ pbjs.setConfig({ 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 + refreshInSeconds: 7200 // refresh ID every 2 hours to ensure it's fresh } }], auctionDelay: 50 // 50ms maximum auction delay, applies to all userId modules @@ -61,7 +61,7 @@ pbjs.setConfig({ | 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` | +| storage.refreshInSeconds | Optional | Integer | How many seconds until the ID5 ID will be refreshed. ID5 strongly recommends 2 hours between refreshes | `7200` | **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). @@ -73,3 +73,68 @@ To turn on A/B Testing, simply edit the configuration (see above table) to enabl ### A Note on Using Multiple Wrappers If you or your monetization partners are deploying multiple Prebid wrappers on your websites, you should make sure you add the ID5 ID User ID module to *every* wrapper. Only the bidders configured in the Prebid wrapper where the ID5 ID User ID module is installed and configured will be able to pick up the ID5 ID. Bidders from other Prebid instances will not be able to pick up the ID5 ID. + +### Provided eids +The module provides following eids: + +``` +[ + { + source: 'id5-sync.com', + uids: [ + { + id: 'some-random-id-value', + atype: 1, + ext: { + linkType: 2, + abTestingControlGroup: false + } + } + ] + }, + { + source: 'true-link-id5-sync.com', + uids: [ + { + id: 'some-publisher-true-link-id', + atype: 1 + } + ] + }, + { + source: 'uidapi.com', + uids: [ + { + id: 'some-uid2', + atype: 3, + ext: { + provider: 'id5-sync.com' + } + } + ] + } +] +``` + +The id from `id5-sync.com` should be always present (though the id provided will be '0' in case of no consent or optout) + +The id from `true-link-id5-sync.com` will be available if the page is integrated with TrueLink (if you are an ID5 partner you can learn more at https://wiki.id5.io/en/identitycloud/retrieve-id5-ids/true-link-integration) + +The id from `uidapi.com` will be available if the partner that is used in ID5 user module has the EUID2 integration enabled (it has to be enabled on the ID5 side) + + +### Providing TrueLinkId as a Google PPID + +TrueLinkId can be provided as a PPID - to use, it the `true-link-id5-sync.com` needs to be provided as a ppid source in prebid userSync configuration: + +```javascript +pbjs.setConfig({ + userSync: { + ppid: 'true-link-id5-sync.com', + userIds: [], //userIds modules should be configured here + } +}); +``` + + + diff --git a/modules/idImportLibrary.js b/modules/idImportLibrary.js index e1847edfce4..8cfa7e47c7c 100644 --- a/modules/idImportLibrary.js +++ b/modules/idImportLibrary.js @@ -1,15 +1,19 @@ -import { logInfo, logError } from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {ajax} from '../src/ajax.js'; -import {config} from '../src/config.js'; import MD5 from 'crypto-js/md5.js'; +import { ACTIVITY_ENRICH_UFPD } from '../src/activities/activities.js'; +import { activityParams } from '../src/activities/activityParams.js'; +import { MODULE_TYPE_PREBID } from '../src/activities/modules.js'; +import { isActivityAllowed } from '../src/activities/rules.js'; +import { ajax } from '../src/ajax.js'; +import { config } from '../src/config.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { logError, logInfo } from '../src/utils.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 CONF_DEFAULT_INPUT_SCAN = false; +export const CONF_DEFAULT_FULL_BODY_SCAN = false; +export const CONF_DEFAULT_INPUT_SCAN = false; const OBSERVER_CONFIG = { subtree: true, attributes: true, @@ -155,7 +159,7 @@ function handleTargetElement() { const targetElement = document.getElementById(conf.target); if (targetElement) { - email = targetElement.innerText; + email = targetElement.textContent; if (!email) { _logInfo('Finding the email with observer'); @@ -257,6 +261,10 @@ export function setConfig(config) { _logError('The required url is not configured'); return; } + if (!isActivityAllowed(ACTIVITY_ENRICH_UFPD, activityParams(MODULE_TYPE_PREBID, 'idImportLibrary'))) { + _logError('Permission for id import was denied by CMP'); + return; + } if (typeof config.debounce !== 'number') { config.debounce = CONF_DEFAULT_OBSERVER_DEBOUNCE_MS; _logInfo('Set default observer debounce to ' + CONF_DEFAULT_OBSERVER_DEBOUNCE_MS); diff --git a/modules/idWardRtdProvider.js b/modules/idWardRtdProvider.js deleted file mode 100644 index 8e6e3c20a64..00000000000 --- a/modules/idWardRtdProvider.js +++ /dev/null @@ -1,10 +0,0 @@ -/** - * This module adds the ID Ward RTD provider to the real time data module - * The {@link module:modules/realTimeData} module is required - * The module will populate real-time data from ID Ward - * @module modules/idWardRtdProvider - * @requires module:modules/realTimeData - */ -import { createRtdProvider } from './anonymisedRtdProvider.js';/* eslint prebid/validate-imports: "off" */ - -export const { getRealTimeData, rtdSubmodule: idWardRtdSubmodule, storage } = createRtdProvider('idWard'); diff --git a/modules/idWardRtdProvider.md b/modules/idWardRtdProvider.md deleted file mode 100644 index 1c9f0654de6..00000000000 --- a/modules/idWardRtdProvider.md +++ /dev/null @@ -1,51 +0,0 @@ -> **Warning!** -> -> The **idWardRtdProvider** module has been renamed to [anonymisedRtdProvider](anonymisedRtdProvider.md) in light of the company's rebranding. -> **idWardRtdProvider** module is maintained for backward compatibility until the next major Prebid release. -> -> Please use anonymisedRtdProvider instead of idWardRtdProvider in your Prebid integration. - -### Overview - -ID Ward is a data anonymization technology for privacy-preserving advertising. Publishers and advertisers are able to target and retarget custom audience segments covering 100% of consented audiences. -ID Ward’s Real-time Data Provider automatically obtains segment IDs from the ID Ward on-domain script (via localStorage) and passes them to the bid-stream. - -### Integration - - 1) Build the idWardRtd module into the Prebid.js package with: - - ``` - gulp build --modules=idWardRtdProvider,... - ``` - - 2) Use `setConfig` to instruct Prebid.js to initilaize the idWardRtdProvider module, as specified below. - -### Configuration - -``` - pbjs.setConfig({ - realTimeData: { - dataProviders: [ - { - name: "idWard", - waitForIt: true, - params: { - cohortStorageKey: "cohort_ids", - segtax: , - } - } - ] - } - }); - ``` - -Please note that idWardRtdProvider should be integrated into the publisher website along with the [ID Ward Pixel](https://publishers-web.id-ward.com/pixel-integration). -Please reach out to Id Ward representative(support@id-ward.com) if you have any questions or need further help to integrate Prebid, idWardRtdProvider, and Id Ward Pixel - -### Testing -To view an example of available segments returned by Id Ward: -``` -‘gulp serve --modules=rtdModule,idWardRtdProvider,pubmaticBidAdapter -``` -and then point your browser at: -"http://localhost:9999/integrationExamples/gpt/idward_segments_example.html" \ No newline at end of file diff --git a/modules/idxBidAdapter.js b/modules/idxBidAdapter.js index 48739275788..12adb4058ae 100644 --- a/modules/idxBidAdapter.js +++ b/modules/idxBidAdapter.js @@ -1,6 +1,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js' import { BANNER } from '../src/mediaTypes.js' import { isArray, isNumber } from '../src/utils.js' +import { interpretResponse } from '../libraries/precisoUtils/bidUtils.js'; const BIDDER_CODE = 'idx' const ENDPOINT_URL = 'https://dev-event.dxmdp.com/rest/api/v1/bid' @@ -49,32 +50,7 @@ export const spec = { } } }, - interpretResponse: function (serverResponse) { - const response = serverResponse.body - - const bids = [] - - response.seatbid.forEach(seat => { - seat.bid.forEach(bid => { - bids.push({ - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - ad: bid.adm, - currency: 'USD', - netRevenue: true, - ttl: 300, - meta: { - advertiserDomains: bid.adomain || [], - }, - }) - }) - }) - - return bids - }, + interpretResponse, } registerBidder(spec) diff --git a/modules/illuminBidAdapter.js b/modules/illuminBidAdapter.js index 45b74bec5c9..0e76e471f4b 100644 --- a/modules/illuminBidAdapter.js +++ b/modules/illuminBidAdapter.js @@ -1,328 +1,27 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} 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 {config} from '../src/config.js'; +import { + isBidRequestValid, createUserSyncGetter, createInterpretResponseFn, createBuildRequestsFn +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'illumin'; const BIDDER_VERSION = '1.0.0'; const GVLID = 149; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.illumin.com`; } -export function extractCID(params) { - return params.cId; -} - -export function extractPID(params) { - return params.pId; -} - -export function extractSubDomain(params) { - return params.subDomain; -} - -function isBidRequestValid(bid) { - const params = bid.params || {}; - return !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.illumin.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.illumin.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.illumin.com/api/sync/iframe', imageSyncUrl: 'https://sync.illumin.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/imdsBidAdapter.js b/modules/imdsBidAdapter.js index 4cad1d614c5..0a0514df205 100644 --- a/modules/imdsBidAdapter.js +++ b/modules/imdsBidAdapter.js @@ -11,7 +11,7 @@ const BID_SCHEME = 'https://'; const BID_DOMAIN = 'technoratimedia.com'; const USER_SYNC_IFRAME_URL = 'https://ad-cdn.technoratimedia.com/html/usersync.html'; const USER_SYNC_PIXEL_URL = 'https://sync.technoratimedia.com/services'; -const VIDEO_PARAMS = [ 'minduration', 'maxduration', 'startdelay', 'placement', 'linearity', 'mimes', 'protocols', 'api' ]; +const VIDEO_PARAMS = [ 'minduration', 'maxduration', 'startdelay', 'placement', 'plcmt', 'linearity', 'mimes', 'protocols', 'api' ]; const BLOCKED_AD_SIZES = [ '1x1', '1x2' diff --git a/modules/impactifyBidAdapter.js b/modules/impactifyBidAdapter.js index ea446bd150d..fcd9360d973 100644 --- a/modules/impactifyBidAdapter.js +++ b/modules/impactifyBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { deepAccess, deepSetValue, generateUUID } from '../src/utils.js'; +import { deepAccess, deepSetValue, generateUUID, isPlainObject } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { ajax } from '../src/ajax.js'; @@ -21,6 +21,7 @@ const DEFAULT_VIDEO_WIDTH = 640; const DEFAULT_VIDEO_HEIGHT = 360; const ORIGIN = 'https://sonic.impactify.media'; const LOGGER_URI = 'https://logger.impactify.media'; +const LOGGER_JS_URI = 'https://log.impactify.it' const AUCTION_URI = '/bidder'; const COOKIE_SYNC_URI = '/static/cookie_sync.html'; const GVL_ID = 606; @@ -97,7 +98,7 @@ const helpers = { mediaType: '*', size: '*' }); - if (typeof floorInfo === 'object' && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === DEFAULT_CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { return parseFloat(floorInfo.floor); } return null; @@ -167,11 +168,6 @@ function createOpenRtbRequest(validBidRequests, bidderRequest) { } deepSetValue(request, 'regs.ext.gdpr', gdprApplies); - if (bidderRequest.uspConsent) { - deepSetValue(request, 'regs.ext.us_privacy', bidderRequest.uspConsent); - this.syncStore.uspConsent = bidderRequest.uspConsent; - } - if (GET_CONFIG('coppa') == true) deepSetValue(request, 'regs.coppa', 1); if (bidderRequest.uspConsent) { @@ -389,6 +385,19 @@ export const spec = { }); return true; - } + }, + + /** + * Register bidder specific code, which will execute if the bid request failed + * @param {*} param0 + */ + onBidderError: function ({ error, bidderRequest }) { + ajax(`${LOGGER_JS_URI}/logger`, null, JSON.stringify({ error, bidderRequest }), { + method: 'POST', + contentType: 'application/json' + }); + + return true; + }, }; registerBidder(spec); diff --git a/modules/impactifyBidAdapter.md b/modules/impactifyBidAdapter.md index de3373395dc..e08f2f3ce01 100644 --- a/modules/impactifyBidAdapter.md +++ b/modules/impactifyBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Impactify Bidder Adapter Module Type: Bidder Adapter -Maintainer: thomas.destefano@impactify.io +Maintainer: programmatic@impactify.io ``` # Description diff --git a/modules/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index fbceeee6d90..33ba08e8fd2 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -3,9 +3,17 @@ 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 {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; +import {convertCurrency} from '../libraries/currencyUtils/currency.js'; +/** + * See https://github.com/prebid/Prebid.js/pull/8827 for details on linting exception + * ImproveDigital only imports after winning a bid and only if the creative cannot reach top + * Also see https://github.com/prebid/Prebid.js/issues/11656 + */ +// eslint-disable-next-line no-restricted-imports import {loadExternalScript} from '../src/adloader.js'; +import { MODULE_TYPE_BIDDER } from '../src/activities/modules.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -23,13 +31,10 @@ const BASIC_ADS_BASE_URL = 'https://ad.360yield-basic.com'; const PB_ENDPOINT = 'pb'; const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; +const DEFAULT_CURRENCY = 'USD'; const VIDEO_PARAMS = { - DEFAULT_MIMES: ['video/mp4'], - PLACEMENT_TYPE: { - INSTREAM: 1, - OUTSTREAM: 3, - } + DEFAULT_MIMES: ['video/mp4'] }; export const spec = { @@ -46,7 +51,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return !!(bid && bid.params && (bid.params.placementId || (bid.params.placementKey && bid.params.publisherId))); + return !!(bid && bid.params && bid.params.placementId && bid.params.publisherId); }, /** @@ -122,6 +127,21 @@ export const spec = { registerBidder(spec); +const convertBidFloorCurrency = (imp) => { + try { + const bidFloor = convertCurrency( + imp.bidfloor, + imp.bidfloorcur, + DEFAULT_CURRENCY, + false, + ); + imp.bidfloor = bidFloor; + imp.bidfloorcur = DEFAULT_CURRENCY; + } catch (err) { + logWarn(`Failed to convert bid floor to ${DEFAULT_CURRENCY}. Passing floor price in its original currency.`, err); + } +}; + export const CONVERTER = ortbConverter({ context: { ttl: CREATIVE_TTL, @@ -133,21 +153,22 @@ export const CONVERTER = ortbConverter({ }, imp(buildImp, bidRequest, context) { const imp = buildImp(bidRequest, context); - imp.secure = Number(window.location.protocol === 'https:'); + imp.secure = bidRequest.ortb2Imp?.secure ?? 1; if (!imp.bidfloor && bidRequest.params.bidFloor) { imp.bidfloor = bidRequest.params.bidFloor; - imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || 'USD' + imp.bidfloorcur = getBidIdParameter('bidFloorCur', bidRequest.params).toUpperCase() || DEFAULT_CURRENCY; + } + + if (imp.bidfloor && imp.bidfloorcur && imp.bidfloorcur !== DEFAULT_CURRENCY) { + convertBidFloorCurrency(imp); } const bidderParamsPath = context.extendMode ? 'ext.prebid.bidder.improvedigital' : 'ext.bidder'; const placementId = bidRequest.params.placementId; - if (placementId) { - deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); - if (context.extendMode) { - deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); - } - } else { - deepSetValue(imp, `${bidderParamsPath}.publisherId`, getBidIdParameter('publisherId', bidRequest.params)); - deepSetValue(imp, `${bidderParamsPath}.placementKey`, getBidIdParameter('placementKey', bidRequest.params)); + const publisherId = bidRequest.params.publisherId; + deepSetValue(imp, `${bidderParamsPath}.placementId`, placementId); + deepSetValue(imp, `${bidderParamsPath}.publisherId`, publisherId); + if (context.extendMode) { + deepSetValue(imp, 'ext.prebid.storedrequest.id', '' + placementId); } deepSetValue(imp, `${bidderParamsPath}.keyValues`, getBidIdParameter('keyValues', bidRequest.params) || undefined); @@ -210,9 +231,9 @@ export const CONVERTER = ortbConverter({ overrides: { imp: { banner(fillImpBanner, imp, bidRequest, context) { - // override to disregard banner.sizes if usePrebidSizes is not set + // override to disregard banner.sizes if usePrebidSizes is false if (!bidRequest.mediaTypes[BANNER]) return; - if (config.getConfig('improvedigital.usePrebidSizes') !== true) { + if (config.getConfig('improvedigital.usePrebidSizes') === false) { const banner = Object.assign({}, bidRequest.mediaTypes[BANNER], {sizes: null}); bidRequest = {...bidRequest, mediaTypes: {[BANNER]: banner}} } @@ -232,33 +253,6 @@ export const CONVERTER = ortbConverter({ context ); deepSetValue(imp, 'ext.is_rewarded_inventory', (video.rewarded === 1 || deepAccess(video, 'ext.rewarded') === 1) || undefined); - if (!imp.video.placement && ID_REQUEST.isOutstreamVideo(bidRequest)) { - // fillImpVideo will have already set placement = 1 for instream - imp.video.placement = VIDEO_PARAMS.PLACEMENT_TYPE.OUTSTREAM; - } - } - }, - request: { - gdprAddtlConsent(setAddtlConsent, ortbRequest, bidderRequest) { - const additionalConsent = bidderRequest?.gdprConsent?.addtlConsent; - if (!additionalConsent) { - return; - } - if (spec.syncStore.extendMode) { - setAddtlConsent(ortbRequest, bidderRequest); - return; - } - if (additionalConsent && additionalConsent.indexOf('~') !== -1) { - // Google Ad Tech Provider IDs - const atpIds = additionalConsent.substring(additionalConsent.indexOf('~') + 1); - if (atpIds) { - deepSetValue( - ortbRequest, - 'user.ext.consented_providers_settings.consented_providers', - atpIds.split('.').map(id => parseInt(id, 10)) - ); - } - } } } } @@ -424,7 +418,7 @@ const ID_RAZR = { ns.q.push(data); if (!ns.loaded) { - loadExternalScript(ID_RAZR.RENDERER_URL, BIDDER_CODE); + loadExternalScript(ID_RAZR.RENDERER_URL, MODULE_TYPE_BIDDER, BIDDER_CODE); } }); diff --git a/modules/improvedigitalBidAdapter.md b/modules/improvedigitalBidAdapter.md index 15602d11038..7206dd8ba7b 100644 --- a/modules/improvedigitalBidAdapter.md +++ b/modules/improvedigitalBidAdapter.md @@ -17,6 +17,7 @@ Module that connects to Improve Digital's demand sources { bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053688 } } @@ -27,6 +28,7 @@ Module that connects to Improve Digital's demand sources bids: [{ bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053689, keyValues: { testKey: ["testValue"] @@ -39,6 +41,7 @@ Module that connects to Improve Digital's demand sources bids: [{ bidder: 'improvedigital', params: { + publisherId: 123, placementId:1053687, size: { w:300, @@ -47,4 +50,4 @@ Module that connects to Improve Digital's demand sources } }] }]; -``` \ No newline at end of file +``` diff --git a/modules/imuIdSystem.js b/modules/imuIdSystem.js index 1242ca183ea..3e9904c526f 100644 --- a/modules/imuIdSystem.js +++ b/modules/imuIdSystem.js @@ -166,6 +166,7 @@ export const imuIdSubmodule = { } }; }, + primaryIds: ['imppid', 'imuid'], eids: { 'imppid': { source: 'ppid.intimatemerger.com', diff --git a/modules/incrxBidAdapter.js b/modules/incrxBidAdapter.js index 9b939aff11b..92059f18149 100644 --- a/modules/incrxBidAdapter.js +++ b/modules/incrxBidAdapter.js @@ -1,7 +1,8 @@ import { parseSizesInput, isEmpty } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js' - +import { BANNER, VIDEO } from '../src/mediaTypes.js' +import { OUTSTREAM } from '../src/video.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -15,7 +16,7 @@ const CREATIVE_TTL = 300; export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -26,7 +27,9 @@ export const spec = { isBidRequestValid: function (bid) { return !!(bid.params.placementId); }, - + hasTypeVideo(bid) { + return typeof bid.mediaTypes !== 'undefined' && typeof bid.mediaTypes.video !== 'undefined'; + }, /** * Make a server request from the list of BidRequests. * @@ -37,17 +40,29 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { return validBidRequests.map(bidRequest => { const sizes = parseSizesInput(bidRequest.params.size || bidRequest.sizes); - + let mdType = 0; + if (bidRequest.mediaTypes[BANNER]) { + mdType = 1; + } else { + mdType = 2; + } const requestParams = { _vzPlacementId: bidRequest.params.placementId, sizes: sizes, _slotBidId: bidRequest.bidId, - // TODO: is 'page' the right value here? _rqsrc: bidderRequest.refererInfo.page, + mChannel: mdType }; - - const payload = { - q: encodeURI(JSON.stringify(requestParams)) + let payload; + if (mdType === 1) { // BANNER + payload = { + q: encodeURI(JSON.stringify(requestParams)) + }; + } else { // VIDEO or other types + payload = { + q: encodeURI(JSON.stringify(requestParams)), + bidderRequestData: encodeURI(JSON.stringify(bidderRequest)) + }; } return { @@ -64,30 +79,81 @@ export const spec = { * @param {ServerResponse} serverResponse A successful response from the server. * @return {Bid[]} An array of bids which were nested inside the server. */ - interpretResponse: function (serverResponse) { + interpretResponse: function (serverResponse, bidderRequest) { const response = serverResponse.body; const bids = []; if (isEmpty(response)) { return bids; } + let decodedBidderRequestData; + if (typeof bidderRequest.data.bidderRequestData === 'string') { + decodedBidderRequestData = JSON.parse(decodeURI(bidderRequest.data.bidderRequestData)); + } else { + decodedBidderRequestData = bidderRequest.data.bidderRequestData; + } const responseBid = { requestId: response.slotBidId, - cpm: response.cpm, + cpm: response.cpm > 0 ? response.cpm : 0, currency: response.currency || DEFAULT_CURRENCY, adType: response.adType || '1', settings: response.settings, - width: response.adWidth, - height: response.adHeight, + width: response.adWidth || 300, + height: response.adHeight || 250, ttl: CREATIVE_TTL, creativeId: response.creativeId || 0, netRevenue: response.netRevenue || false, + mediaType: response.mediaType || BANNER, meta: { - mediaType: response.mediaType || BANNER, + mediaType: response.mediaType, advertiserDomains: response.advertiserDomains || [] }, - ad: response.ad + }; + if (response.mediaType === BANNER) { + responseBid.ad = response.ad || ''; + } else if (response.mediaType === VIDEO) { + let context, adUnitCode; + for (let i = 0; i < decodedBidderRequestData.bids.length; i++) { + const item = decodedBidderRequestData.bids[i]; + if (item.bidId === response.slotBidId) { + context = item.mediaTypes.video.context; + adUnitCode = item.adUnitCode; + break; + } + } + if (context === OUTSTREAM) { + responseBid.vastXml = response.ad || ''; + if (response.rUrl) { + responseBid.renderer = createRenderer({ ...response, adUnitCode }); + } + } + } bids.push(responseBid); + function createRenderer(bid, rendererOptions = {}) { + const renderer = Renderer.install({ + id: bid.slotBidId, + url: bid.rUrl, + config: rendererOptions, + adUnitCode: bid.adUnitCode, + loaded: false + }); + try { + 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; + } return bids; } diff --git a/modules/incrxBidAdapter.md b/modules/incrxBidAdapter.md new file mode 100644 index 00000000000..b37d1e6b566 --- /dev/null +++ b/modules/incrxBidAdapter.md @@ -0,0 +1,45 @@ +# Overview + +``` +Module Name: IncrementX Bid Adapter +Module Type: Bidder Adapter +Maintainer: prebid-team@vertoz.com +``` + +# Description + +IncrementX Bid Adapter supports banner and video at present. + +# Test Parameters +``` + var adUnits = [ + { + code: "banner-space", + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: "incrementx", + params: { + placementId: "your_placementId" // required, + } + }] + }, { + code: 'video-outstream-space', + mediaTypes: { + video: { + context: "outstream", + playerSize: [640,480] + } + }, + bids: [{ + bidder: "incrementx", + params: { + placementId: "your_placement_id" // required, + } + }] + }]; + +``` diff --git a/modules/inmobiBidAdapter.js b/modules/inmobiBidAdapter.js new file mode 100644 index 00000000000..910c53bf838 --- /dev/null +++ b/modules/inmobiBidAdapter.js @@ -0,0 +1,347 @@ +import { deepAccess, deepSetValue, isFn } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; +import { ortb25Translator } from '../libraries/ortb2.5Translator/translator.js'; +import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderSpec} BidderSpec + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid + */ + +const GVLID = 333; +export const ADAPTER_VERSION = 1.0; +const BIDDER_CODE = 'inmobi'; +const BID_ENDPOINT = 'https://api.w.inmobi.com/openrtb/bidder/prebidjs'; +export const EVENT_ENDPOINT = 'https://sync.inmobi.com'; +export const SYNC_ENDPOINT = 'https://sync.inmobi.com/prebidjs?'; +const TRANSLATOR = ortb25Translator(); +const CURRENCY = 'USD'; +const POST_METHOD = 'POST'; +const PLAIN_CONTENT_TYPE = 'text/plain'; + +/** + * Defines the core oRTB converter inherited from converter library and all customization functions. + */ +const CONVERTER = ortbConverter({ + context: { + netRevenue: true, + ttl: 3600, + currency: CURRENCY + }, + imp, + request, + bidResponse, + response +}); + +/** + * Builds an impression object for oRTB 2.5 requests based on the bid request. + * + * @param {function} buildImp - Function to build the imp object. + * @param {Object} bidRequest - The request containing bid details. + * @param {Object} context - Context for the impression. + * @returns {Object} The constructed impression object. + */ +function imp(buildImp, bidRequest, context) { + let imp = buildImp(bidRequest, context); + const params = bidRequest.params; + + imp.tagid = bidRequest.adUnitCode; + let floorInfo = {}; + + if (isFn(bidRequest.getFloor)) { + floorInfo = bidRequest.getFloor({ + currency: CURRENCY, + size: '*', + mediaType: '*' + }); + } + + // if floor price module is not set reading from bidRequest.params + if (!imp.bidfloor && bidRequest.params.bidfloor) { + imp.bidfloor = bidRequest.params.bidfloor; + imp.bidfloorcur = CURRENCY; + } + + deepSetValue(imp, 'ext', { + ...imp.ext, + params: bidRequest.params, + bidder: { + plc: params?.plc, + }, + moduleFloors: floorInfo + }); + imp.secure = Number(window.location.protocol === 'https:'); + + return imp; +} + +/** + * Constructs the oRTB 2.5 request object. + * + * @param {function} buildRequest - Function to build the request. + * @param {Array} imps - Array of impression objects. + * @param {Object} bidderRequest - Object containing bidder request information. + * @param {Object} context - Additional context. + * @returns {Object} The complete oRTB request object. + */ +function request(buildRequest, imps, bidderRequest, context) { + let request = buildRequest(imps, bidderRequest, context); + + deepSetValue(request, 'ext.prebid.channel.name', 'pbjs_InMobi'); + deepSetValue(request, 'ext.prebid.channel.pbjsversion', '$prebid.version$'); + deepSetValue(request, 'ext.prebid.channel.adapterversion', ADAPTER_VERSION); + + request = TRANSLATOR(request); + return request; +} + +/** + * Transforms an oRTB 2.5 bid into a bid response format for Prebid.js. + * + * @param {function} buildBidResponse - Function to build a bid response. + * @param {Object} bid - The bid to be transformed. + * @param {Object} context - Context for the bid. + * @returns {Object} Formatted bid response. + */ +function bidResponse(buildBidResponse, bid, context) { + context.mtype = deepAccess(bid, 'mtype'); + if (context.mtype === 4) { + const admJson = JSON.parse(bid.adm); + bid.adm = JSON.stringify(admJson.native); + } + let bidResponse = buildBidResponse(bid, context); + + if (typeof deepAccess(bid, 'ext') !== 'undefined') { + deepSetValue(bidResponse, 'meta', { + ...bidResponse.meta, + ...bid.ext, + }); + } + + return bidResponse; +} + +/** + * Converts the oRTB 2.5 bid response into the format required by Prebid.js. + * + * @param {function} buildResponse - Function to build the response. + * @param {Array} bidResponses - List of bid responses. + * @param {Object} ortbResponse - Original oRTB response data. + * @param {Object} context - Additional context. + * @returns {Object} Prebid.js compatible bid response. + */ +function response(buildResponse, bidResponses, ortbResponse, context) { + let response = buildResponse(bidResponses, ortbResponse, context); + + return response; +} + +/** @type {BidderSpec} */ +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + /** + * Determines user sync options based on consent and supported sync types. + * + * @param {Object} syncOptions - Options for user syncing (iframe, pixel). + * @param {Array} responses - List of bid responses. + * @param {Object} gdprConsent - GDPR consent details. + * @param {Object} uspConsent - CCPA consent details. + * @param {Object} gppConsent - GPP consent details. + * @returns {Array} List of user sync URLs. + */ + getUserSyncs: (syncOptions, responses, gdprConsent, uspConsent, gppConsent) => { + const urls = []; + if (!syncOptions.iframeEnabled && !syncOptions.pixelEnabled) { + return urls; + } + const pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + + let query = ''; + if (gdprConsent) { + query = tryAppendQueryString(query, 'gdpr', (gdprConsent.gdprApplies ? 1 : 0)); + } + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + query = tryAppendQueryString(query, 'gdpr_consent', gdprConsent.consentString); + } + if (uspConsent) { + query = tryAppendQueryString(query, 'us_privacy', uspConsent); + } + if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { + query = tryAppendQueryString(query, 'gpp', gppConsent.gppString); + query = tryAppendQueryString(query, 'gpp_sid', gppConsent?.applicableSections?.join(',')); + } + if (query.slice(-1) === '&') { + query = query.slice(0, -1); + } + + if (pixelType === 'iframe' || (!responses || responses.length === 0)) { + return [{ + type: pixelType, + url: SYNC_ENDPOINT + query + }]; + } else { + responses.forEach(resp => { + const userSyncs = deepAccess(resp, 'body.ext.prebidjs.urls'); + if (!userSyncs) { + return; + } + + userSyncs.forEach(us => { + let url = us.url; + if (query) { + url = url + (url.indexOf('?') === -1 ? '?' : '&') + query; + } + + urls.push({ + type: pixelType, + url: url + }); + }); + }); + return urls; + } + }, + + /** + * Validates if a bid request contains the required parameters for InMobi. + * + * @param {Object} bid - Bid request to validate. + * @returns {boolean} True if the bid request is valid, otherwise false. + */ + isBidRequestValid: (bid) => { + if (!(bid && bid.params && bid.params.plc)) { + return false; + } + + return true; + }, + + /** + * Builds the server request from bid requests for InMobi. + * + * @param {BidRequest[]} bidRequests - Array of bid requests. + * @param {Object} bidderRequest - Additional request details. + * @returns {ServerRequest} The server request for bidding. + */ + buildRequests: (bidRequests, bidderRequest) => { + const data = CONVERTER.toORTB({ bidderRequest, bidRequests }); + + if (data) { + const requestPayload = { + method: POST_METHOD, + url: BID_ENDPOINT, + data: data, + options: { + contentType: PLAIN_CONTENT_TYPE, + crossOrigin: true, + withCredentials: true + } + }; + return requestPayload; + } + }, + + /** + * Interprets the server response and formats it into bids. + * + * @param {Object} response - Response from the server. + * @param {ServerRequest} request - Original bid request. + * @returns {Bid[]} Parsed bids or configurations. + */ + interpretResponse: (response, request) => { + if (typeof response?.body == 'undefined') { + return []; + } + + const interpretedResponse = CONVERTER.fromORTB({ response: response.body, request: request.data }); + const bids = interpretedResponse.bids || []; + + return bids; + }, + + /** + * Callback to report timeout event. + * + * @param {TimedOutBid[]} timeoutData - Array of timeout details. + */ + onTimeout: (timeoutData) => { + report('onTimeout', timeoutData); + }, + + /** + * Callback to report targeting event. + * + * @param {Bid} bid - The bid object + */ + onSetTargeting: (bid) => { + report('onSetTargeting', bid?.meta); + }, + + /** + * Callback to report successful ad render event. + * + * @param {Bid} bid - The bid that successfully rendered. + */ + onAdRenderSucceeded: (bid) => { + report('onAdRenderSucceeded', bid?.meta); + }, + + /** + * Callback to report bidder error event. + * + * @param {Object} errorData - Details about the error. + */ + onBidderError: (errorData) => { + report('onBidderError', errorData); + }, + + /** + * Callback to report bid won event. + * + * @param {Bid} bid - The bid that won the auction. + */ + onBidWon: (bid) => { + report('onBidWon', bid?.meta); + } + +}; + +function isReportingAllowed(loggingPercentage) { + return loggingPercentage != 0; +} + +function report(type, data) { + if (!data) { + return; + } + if (['onBidWon', 'onAdRenderSucceeded', 'onSetTargeting'].includes(type) && !isReportingAllowed(data.loggingPercentage)) { + return; + } + const payload = JSON.stringify({ + domain: location.hostname, + eventPayload: data + }); + + fetch(`${EVENT_ENDPOINT}/report/${type}`, { + body: payload, + keepalive: true, + credentials: 'include', + method: POST_METHOD, + headers: { + 'Content-Type': PLAIN_CONTENT_TYPE + } + }).catch((_e) => { + // do nothing; ignore errors + }); +} + +registerBidder(spec); diff --git a/modules/inmobiBidAdapter.md b/modules/inmobiBidAdapter.md new file mode 100644 index 00000000000..bf309fcb01d --- /dev/null +++ b/modules/inmobiBidAdapter.md @@ -0,0 +1,87 @@ +# Overview + +Module Name: InMobi Bidder Adapter +Module Type: Bidder Adapter +Maintainer: prebid-support@inmobi.com + +# Description + +Module that connects to InMobi's demand sources. + +# Bid Params + +| Name | Scope | Description | Example | Type | +|------------|----------|--------------|-----------|-------- | +| `plc` | required | Placement ID | `'1234'` | `string` | +| `bidfloor` | optional | Bid Floor | `1.2` | `float` | + + +# Test Parameters + +## Banner +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + + bids: [{ + bidder: 'inmobi', + params: { + plc: '1719108420057' // Mandatory + } + }] + + }]; +``` + +## Video +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + video: { + playerSize : [300,250], + mimes : ["video/x-ms-wmv", "video/mp4"], + minduration : 0, + maxduration: 30, + protocols : [1,2], + api: [1, 2, 4, 6], + protocols: [3, 4, 7, 8, 10], + placement: 1, + plcmt: 1 + } + }, + + // Replace this object to test a new Adapter! + bids: [{ + bidder: 'inmobi', + params: { + plc: '1443164204446401' //Mandatory + } + }] + }]; +``` + +## Native +``` + var adUnits = [{ + code: 'div-gpt-ad-1460505748561-0', + mediaTypes: { + native: { + type: 'image' + } + }, + + bids: [{ + bidder: 'inmobi', + params: { + plc: '10000033152', + bidfloor: 0.9 + } + }] + }]; +``` \ No newline at end of file diff --git a/modules/insticatorBidAdapter.js b/modules/insticatorBidAdapter.js index 617ce49f171..4ab7f5927f9 100644 --- a/modules/insticatorBidAdapter.js +++ b/modules/insticatorBidAdapter.js @@ -1,7 +1,7 @@ import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue} from '../src/utils.js'; +import {deepAccess, generateUUID, logError, isArray, isInteger, isArrayOfNums, deepSetValue, isFn, logWarn} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {find} from '../src/polyfill.js'; @@ -102,24 +102,13 @@ function buildVideo(bidRequest) { let w = deepAccess(bidRequest, 'mediaTypes.video.w'); let h = deepAccess(bidRequest, 'mediaTypes.video.h'); const mimes = deepAccess(bidRequest, 'mediaTypes.video.mimes'); - const placement = deepAccess(bidRequest, 'mediaTypes.video.placement') || 3; + const placement = deepAccess(bidRequest, 'mediaTypes.video.placement'); const plcmt = deepAccess(bidRequest, 'mediaTypes.video.plcmt') || undefined; const playerSize = deepAccess(bidRequest, 'mediaTypes.video.playerSize'); const context = deepAccess(bidRequest, 'mediaTypes.video.context'); - if (!w && playerSize) { - if (Array.isArray(playerSize[0])) { - w = parseInt(playerSize[0][0], 10); - } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { - w = parseInt(playerSize[0], 10); - } - } - if (!h && playerSize) { - if (Array.isArray(playerSize[0])) { - h = parseInt(playerSize[0][1], 10); - } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { - h = parseInt(playerSize[1], 10); - } + if (!h && !w && playerSize) { + ({w, h} = parsePlayerSizeToWidthHeight(playerSize, w, h)); } const bidRequestVideo = deepAccess(bidRequest, 'mediaTypes.video'); @@ -136,6 +125,10 @@ function buildVideo(bidRequest) { } } + if (placement && typeof placement !== 'undefined' && typeof placement === 'number') { + optionalParams['placement'] = placement; + } + if (plcmt) { optionalParams['plcmt'] = plcmt; } @@ -145,7 +138,6 @@ function buildVideo(bidRequest) { } let videoObj = { - placement, mimes, w, h, @@ -170,6 +162,27 @@ function buildImpression(bidRequest) { }, } + if (bidRequest?.params?.adUnitId) { + deepSetValue(imp, 'ext.prebid.bidder.insticator.adUnitId', bidRequest.params.adUnitId); + } + + if (bidRequest?.params?.publisherId) { + deepSetValue(imp, 'ext.prebid.bidder.insticator.publisherId', bidRequest.params.publisherId); + } + + let bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); + + if (!isNaN(bidFloor)) { + imp.bidfloor = deepAccess(bidRequest, 'params.floor'); + imp.bidfloorcur = 'USD'; + const bidfloorcur = deepAccess(bidRequest, 'params.bidfloorcur') + if (bidfloorcur && bidfloorcur !== 'USD') { + delete imp.bidfloor; + delete imp.bidfloorcur; + logWarn('insticator: bidfloorcur supported by insticator is USD only. ignoring bidfloor and bidfloorcur params'); + } + } + if (deepAccess(bidRequest, 'mediaTypes.banner')) { imp.banner = buildBanner(bidRequest); } @@ -178,6 +191,49 @@ function buildImpression(bidRequest) { imp.video = buildVideo(bidRequest); } + if (isFn(bidRequest.getFloor)) { + let moduleBidFloor; + + const mediaType = deepAccess(bidRequest, 'mediaTypes.banner') ? 'banner' : deepAccess(bidRequest, 'mediaTypes.video') ? 'video' : undefined; + + let _mediaType = mediaType; + let _size = '*'; + + if (mediaType && ['banner', 'video'].includes(mediaType)) { + if (mediaType === 'banner') { + const { w: width, h: height } = imp[mediaType]; + if (width && height) { + _size = [width, height]; + } else { + const sizes = deepAccess(bidRequest, 'mediaTypes.banner.format'); + if (sizes && sizes.length > 0) { + const {w: width, h: height} = sizes[0]; + _size = [width, height]; + } + } + } else if (mediaType === 'video') { + const { w: width, h: height } = imp[mediaType]; + _mediaType = mediaType; + _size = [width, height]; + } + } + try { + moduleBidFloor = bidRequest.getFloor({ + currency: 'USD', + mediaType: _mediaType, + size: _size + }); + } catch (err) { + // continue with no module floors + logWarn('priceFloors module call getFloor failed, error : ', err); + } + + if (moduleBidFloor) { + imp.bidfloor = moduleBidFloor.floor; + imp.bidfloorcur = moduleBidFloor.currency; + } + } + return imp; } @@ -188,7 +244,7 @@ function buildDevice(bidRequest) { const device = { w: window.innerWidth, h: window.innerHeight, - js: true, + js: 1, ext: { localStorage: storage.localStorageIsEnabled(), cookies: storage.cookiesAreEnabled(), @@ -380,6 +436,10 @@ function buildRequest(validBidRequests, bidderRequest) { deepSetValue(req, 'user.ext.consent', bidderRequest.gdprConsent.consentString); } + if (validBidRequests[0]?.params?.publisherId) { + deepSetValue(req, 'site.publisher.id', validBidRequests[0].params.publisherId); + } + return req; } @@ -506,24 +566,12 @@ function validateVideo(bid) { let w = deepAccess(bid, 'mediaTypes.video.w'); let h = deepAccess(bid, 'mediaTypes.video.h'); const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!w && playerSize) { - if (Array.isArray(playerSize[0])) { - w = parseInt(playerSize[0][0], 10); - } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { - w = parseInt(playerSize[0], 10); - } - } - if (!h && playerSize) { - if (Array.isArray(playerSize[0])) { - h = parseInt(playerSize[0][1], 10); - } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { - h = parseInt(playerSize[1], 10); - } + + if (!h && !w && playerSize) { + ({w, h} = parsePlayerSizeToWidthHeight(playerSize, w, h)); } - const videoSize = [ - w, - h, - ]; + + const videoSize = [w, h]; if ( !validateSize(videoSize) @@ -539,13 +587,6 @@ function validateVideo(bid) { return false; } - const placement = deepAccess(bid, 'mediaTypes.video.placement'); - - if (typeof placement !== 'undefined' && typeof placement !== 'number') { - logError('insticator: video placement is not a number'); - return false; - } - const plcmt = deepAccess(bid, 'mediaTypes.video.plcmt'); if (typeof plcmt !== 'undefined' && typeof plcmt !== 'number') { @@ -569,6 +610,25 @@ function validateVideo(bid) { return true; } +function parsePlayerSizeToWidthHeight(playerSize, w, h) { + if (!w && playerSize) { + if (Array.isArray(playerSize[0])) { + w = parseInt(playerSize[0][0], 10); + } else if (typeof playerSize[0] === 'number' && !isNaN(playerSize[0])) { + w = parseInt(playerSize[0], 10); + } + } + if (!h && playerSize) { + if (Array.isArray(playerSize[0])) { + h = parseInt(playerSize[0][1], 10); + } else if (typeof playerSize[1] === 'number' && !isNaN(playerSize[1])) { + h = parseInt(playerSize[1], 10); + } + } + + return { w, h }; +} + export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/modules/instreamTracking.js b/modules/instreamTracking.js index 2686feab679..909c21b29bd 100644 --- a/modules/instreamTracking.js +++ b/modules/instreamTracking.js @@ -41,9 +41,10 @@ const whitelistedResources = /video|fetch|xmlhttprequest|other/; * * Note: this is a workaround till a better approach is engineered. * - * @param {Array} adUnits - * @param {Array} bidsReceived - * @param {Array} bidderRequests + * @param {object} config + * @param {Array} config.adUnits + * @param {Array} config.bidsReceived + * @param {Array} config.bidderRequests * * @return {boolean} returns TRUE if tracking started */ diff --git a/modules/intentIqAnalyticsAdapter.js b/modules/intentIqAnalyticsAdapter.js new file mode 100644 index 00000000000..1cf270117b7 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.js @@ -0,0 +1,324 @@ +import {logError, logInfo} from '../src/utils.js'; +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {config} from '../src/config.js'; +import {EVENTS} from '../src/constants.js'; +import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; +import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; +import {appendVrrefAndFui, getReferrer} from '../libraries/intentIqUtils/getRefferer.js'; +import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY, VERSION} from '../libraries/intentIqConstants/intentIqConstants.js'; + +const MODULE_NAME = 'iiqAnalytics' +const analyticsType = 'endpoint'; +const defaultUrl = 'https://reports.intentiq.com/report'; +const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}); +const prebidVersion = '$prebid.version$'; +export const REPORTER_ID = Date.now() + '_' + getRandom(0, 1000); + +const PARAMS_NAMES = { + abTestGroup: 'abGroup', + pbPauseUntil: 'pbPauseUntil', + pbMonitoringEnabled: 'pbMonitoringEnabled', + isInTestGroup: 'isInTestGroup', + enhanceRequests: 'enhanceRequests', + wasSubscribedForPrebid: 'wasSubscribedForPrebid', + hadEids: 'hadEids', + ABTestingConfigurationSource: 'ABTestingConfigurationSource', + lateConfiguration: 'lateConfiguration', + jsversion: 'jsversion', + eidsNames: 'eidsNames', + requestRtt: 'rtt', + clientType: 'clientType', + adserverDeviceType: 'AdserverDeviceType', + terminationCause: 'terminationCause', + callCount: 'callCount', + manualCallCount: 'mcc', + pubprovidedidsFailedToregister: 'ppcc', + noDataCount: 'noDataCount', + profile: 'profile', + isProfileDeterministic: 'pidDeterministic', + siteId: 'sid', + hadEidsInLocalStorage: 'idls', + auctionStartTime: 'ast', + eidsReadTime: 'eidt', + agentId: 'aid', + auctionEidsLength: 'aeidln', + wasServerCalled: 'wsrvcll', + referrer: 'vrref', + isInBrowserBlacklist: 'inbbl', + prebidVersion: 'pbjsver', + partnerId: 'partnerId', + firstPartyId: 'pcid', + placementId: 'placementId' +}; + +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({defaultUrl, analyticsType}), { + initOptions: { + lsValueInitialized: false, + partner: null, + fpid: null, + currentGroup: null, + dataInLs: null, + eidl: null, + lsIdsInitialized: false, + manualWinReportEnabled: false, + domainName: null + }, + track({eventType, args}) { + switch (eventType) { + case BID_WON: + bidWon(args); + break; + case BID_REQUESTED: + defineGlobalVariableName(); + break; + default: + break; + } + } +}); + +// Events needed +const { + BID_WON, + BID_REQUESTED +} = EVENTS; + +function readData(key) { + try { + if (storage.hasLocalStorage()) { + return storage.getDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled()) { + return storage.getCookie(key); + } + } catch (error) { + logError(error); + } +} + +function initLsValues() { + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) return; + let iiqArr = config.getConfig('userSync.userIds').filter(m => m.name == 'intentIqId'); + if (iiqArr && iiqArr.length > 0) iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized = true; + if (!iiqArr) iiqArr = []; + if (iiqArr.length == 0) { + iiqArr.push({ + 'params': { + 'partner': -1, + 'group': 'U' + } + }) + } + if (iiqArr && iiqArr.length > 0) { + if (iiqArr[0].params && iiqArr[0].params.partner && !isNaN(iiqArr[0].params.partner)) { + iiqAnalyticsAnalyticsAdapter.initOptions.partner = iiqArr[0].params.partner; + } + iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList = typeof iiqArr[0].params.browserBlackList === 'string' ? iiqArr[0].params.browserBlackList.toLowerCase() : ''; + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = iiqArr[0].params.manualWinReportEnabled || false; + iiqAnalyticsAnalyticsAdapter.initOptions.domainName = iiqArr[0].params.domainName || ''; + } +} + +function initReadLsIds() { + try { + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = null; + iiqAnalyticsAnalyticsAdapter.initOptions.fpid = JSON.parse(readData(FIRST_PARTY_KEY)); + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid) { + iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup = iiqAnalyticsAnalyticsAdapter.initOptions.fpid.group; + } + const partnerData = readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + const clientsHints = readData(CLIENT_HINTS_KEY) || ''; + + if (partnerData) { + iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; + let pData = JSON.parse(partnerData); + iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause = pData.terminationCause + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; + iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; + iiqAnalyticsAnalyticsAdapter.initOptions.ct = pData.ct || null; + iiqAnalyticsAnalyticsAdapter.initOptions.siteId = pData.siteId || null; + iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll = pData.wsrvcll || false; + iiqAnalyticsAnalyticsAdapter.initOptions.rrtt = pData.rrtt || null; + } + + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints = clientsHints + } catch (e) { + logError(e) + } +} + +function bidWon(args, isReportExternal) { + if (!iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized) { + initLsValues(); + } + + if (isNaN(iiqAnalyticsAnalyticsAdapter.initOptions.partner) || iiqAnalyticsAnalyticsAdapter.initOptions.partner == -1) return; + + const currentBrowserLowerCase = detectBrowser(); + if (iiqAnalyticsAnalyticsAdapter.initOptions.browserBlackList?.includes(currentBrowserLowerCase)) { + logError('IIQ ANALYTICS -> Browser is in blacklist!'); + return; + } + + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized && !iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + initReadLsIds(); + } + if ((isReportExternal && iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled) || (!isReportExternal && !iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled)) { + ajax(constructFullUrl(preparePayload(args, true)), undefined, null, {method: 'GET'}); + logInfo('IIQ ANALYTICS -> BID WON') + return true; + } + return false; +} + +function defineGlobalVariableName() { + function reportExternalWin(args) { + return bidWon(args, true) + } + + let partnerId = 0 + const userConfig = config.getConfig('userSync.userIds') + + if (userConfig) { + const iiqArr = userConfig.filter(m => m.name == 'intentIqId'); + if (iiqArr.length) partnerId = iiqArr[0].params.partner + } + + window[`intentIqAnalyticsAdapter_${partnerId}`] = {reportExternalWin: reportExternalWin} +} + +function getRandom(start, end) { + return Math.floor((Math.random() * (end - start + 1)) + start); +} + +export function preparePayload(data) { + let result = getDefaultDataObject(); + readData(FIRST_PARTY_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; + result[PARAMS_NAMES.prebidVersion] = prebidVersion; + result[PARAMS_NAMES.referrer] = getReferrer(); + result[PARAMS_NAMES.terminationCause] = iiqAnalyticsAnalyticsAdapter.initOptions.terminationCause; + result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; + result[PARAMS_NAMES.clientType] = iiqAnalyticsAnalyticsAdapter.initOptions.ct; + result[PARAMS_NAMES.siteId] = iiqAnalyticsAnalyticsAdapter.initOptions.siteId; + result[PARAMS_NAMES.wasServerCalled] = iiqAnalyticsAnalyticsAdapter.initOptions.wsrvcll; + result[PARAMS_NAMES.requestRtt] = iiqAnalyticsAnalyticsAdapter.initOptions.rrtt; + + result[PARAMS_NAMES.isInTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup == 'A'; + + result[PARAMS_NAMES.agentId] = REPORTER_ID; + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pcid) result[PARAMS_NAMES.firstPartyId] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid); + if (iiqAnalyticsAnalyticsAdapter.initOptions.fpid?.pid) result[PARAMS_NAMES.profile] = encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pid) + + prepareData(data, result); + + fillEidsData(result); + + return result; +} + +function fillEidsData(result) { + if (iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized) { + result[PARAMS_NAMES.hadEidsInLocalStorage] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl && iiqAnalyticsAnalyticsAdapter.initOptions.eidl > 0; + result[PARAMS_NAMES.auctionEidsLength] = iiqAnalyticsAnalyticsAdapter.initOptions.eidl || -1; + } +} + +function prepareData (data, result) { + if (data.bidderCode) { + result.bidderCode = data.bidderCode; + } + if (data.cpm) { + result.cpm = data.cpm; + } + if (data.currency) { + result.currency = data.currency; + } + if (data.originalCpm) { + result.originalCpm = data.originalCpm; + } + if (data.originalCurrency) { + result.originalCurrency = data.originalCurrency; + } + if (data.status) { + result.status = data.status; + } + if (data.auctionId) { + result.prebidAuctionId = data.auctionId; + } + if (data.placementId) { + result.placementId = data.placementId; + } else { + // Simplified placementId determination + let placeIdFound = false; + if (data.params && Array.isArray(data.params)) { + for (let i = 0; i < data.params.length; i++) { + const param = data.params[i]; + if (param.placementId) { + result.placementId = param.placementId; + placeIdFound = true; + break; + } + } + } + if (!placeIdFound && data.adUnitCode) { + result.placementId = data.adUnitCode; + } + } + + result.biddingPlatformId = 1; + result.partnerAuctionId = 'BW'; +} + +function getDefaultDataObject() { + return { + 'inbbl': false, + 'pbjsver': prebidVersion, + 'partnerAuctionId': 'BW', + 'reportSource': 'pbjs', + 'abGroup': 'U', + 'jsversion': VERSION, + 'partnerId': -1, + 'biddingPlatformId': 1, + 'idls': false, + 'ast': -1, + 'aeidln': -1 + } +} + +function constructFullUrl(data) { + let report = []; + data = btoa(JSON.stringify(data)); + report.push(data); + const gppData = getGppValue(); + + let url = defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + + '&mct=1' + + ((iiqAnalyticsAnalyticsAdapter.initOptions?.fpid) + ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + + '&agid=' + REPORTER_ID + + '&jsver=' + VERSION + + '&source=pbjs' + + '&payload=' + JSON.stringify(report) + + '&uh=' + iiqAnalyticsAnalyticsAdapter.initOptions.clientsHints + + (gppData.gppString ? '&gpp=' + encodeURIComponent(gppData.gppString) : ''); + + url = appendVrrefAndFui(url, iiqAnalyticsAnalyticsAdapter.initOptions.domainName); + return url; +} + +iiqAnalyticsAnalyticsAdapter.originEnableAnalytics = iiqAnalyticsAnalyticsAdapter.enableAnalytics; + +iiqAnalyticsAnalyticsAdapter.enableAnalytics = function (myConfig) { + iiqAnalyticsAnalyticsAdapter.originEnableAnalytics(myConfig); // call the base class function +}; +adapterManager.registerAnalyticsAdapter({ + adapter: iiqAnalyticsAnalyticsAdapter, + code: MODULE_NAME +}); + +export default iiqAnalyticsAnalyticsAdapter; diff --git a/modules/intentIqAnalyticsAdapter.md b/modules/intentIqAnalyticsAdapter.md new file mode 100644 index 00000000000..2a3eece0576 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.md @@ -0,0 +1,85 @@ +# Overview + +Module Name: iiqAnalytics +Module Type: Analytics Adapter +Maintainer: julian@intentiq.com + +# Description + +By using this Intent IQ adapter, you will be able to obtain comprehensive analytics and metrics regarding the performance of the Intent IQ Unified ID module. This includes how the module impacts your revenue, CPMs, and fill rates related to bidders and domains. + +## Intent IQ Universal ID Registration + +No registration for this module is required. + +## Intent IQ Universal IDConfiguration + +IMPORTANT: only effective when Intent IQ Universal ID module is installed and configured. [(How-To)](https://docs.prebid.org/dev-docs/modules/userid-submodules/intentiq.html) + +#### Example Configuration + +```js +pbjs.enableAnalytics({ + provider: 'iiqAnalytics' +}); +``` + + +### Manual Report Trigger with reportExternalWin + +The reportExternalWin function allows for manual reporting, meaning that reports will not be sent automatically but only when triggered manually. + +To enable this manual reporting functionality, you must set the manualWinReportEnabled parameter in Intent IQ Unified ID module configuration is true. Once enabled, reports can be manually triggered using the reportExternalWin function. + + +### Calling the reportExternalWin Function + +To call the reportExternalWin function, you need to pass the partner_id parameter as shown in the example below: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin() +``` +Example use with Partner ID = 123455 + +```js +window.intentIqAnalyticsAdapter_123455.reportExternalWin() +``` + +### Function Parameters + +The reportExternalWin function takes an object containing auction win data. Below is an example of the object: + +```js +var reportData = { +biddingPlatformId: 1, // Platform ID. The value 1 corresponds to PreBid. +partnerAuctionId: '[YOUR_AUCTION_ID_IF_EXISTS]', // Auction ID, if available. +bidderCode: 'xxxxxxxx', // Bidder code. +prebidAuctionId: '3d4xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx8e', // PreBid auction ID. +cpm: 1.5, // Cost per thousand impressions (CPM). +currency: 'USD', // Currency for the CPM value. +originalCpm: 1.5, // Original CPM value. +originalCurrency: 'USD', // Original currency. +status: 'rendered', // Auction status, e.g., 'rendered'. +placementId: 'div-1' // ID of the ad placement. +} +``` + +| Field | Data Type | Description | Example | Mandatory | +|--------------------|-----------|--------------------------------------------------------------------------------------------------------------------------------------------------|-------------------------------|-----------| +| biddingPlatformId | Integer | Specify the platform in which this ad impression was rendered – 1 – Prebid, 2 – Amazon, 3 – Google, 4 – Open RTB (including your local Prebid server) | 1 | Yes | +| partnerAuctionId | String | Use this when you are running multiple auction solutions across your assets and have a unified identifier for auctions | 3d44542d-xx-4662-xxxx-4xxxx3d8e | No | +| bidderCode | String | Specifies the name of the bidder that won the auction as reported by Prebid and all other bidding platforms | newAppnexus | Yes | +| prebidAuctionId | String | Specifies the identifier of the Prebid auction. Leave empty or undefined if Prebid is not the bidding platform | | | +| cpm | Decimal | Cost per mille of the impression as received from the demand-side auction (without modifications or reductions) | 5.62 | Yes | +| currency | String | Currency of the auction | USD | Yes | +| originalCpm | Decimal | Leave empty or undefined if Prebid is not the bidding platform | 5.5 | No | +| originalCurrency | String | Currency of the original auction | USD | No | +| status | String | Status of the impression. Leave empty or undefined if Prebid is not the bidding platform | rendered | No | +| placementId | String | Unique identifier of the ad unit on the webpage that showed this ad | div-1 | No | + + +To report the auction win, call the function as follows: + +```js +window.intentIqAnalyticsAdapter_[partner_id].reportExternalWin(reportData) +``` diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 109ea5c39c6..4a7d0f47b18 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -5,11 +5,27 @@ * @requires module:modules/userId */ -import { logError, logInfo } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; -import { submodule } from '../src/hook.js' -import { getStorageManager } from '../src/storageManager.js'; -import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import {logError, logInfo} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js'; +import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import {uspDataHandler} from '../src/consentHandler.js'; +import AES from 'crypto-js/aes.js'; +import Utf8 from 'crypto-js/enc-utf8.js'; +import {detectBrowser} from '../libraries/intentIqUtils/detectBrowserUtils.js'; +import {appendVrrefAndFui} from '../libraries/intentIqUtils/getRefferer.js'; +import {getGppValue} from '../libraries/intentIqUtils/getGppValue.js'; +import { + FIRST_PARTY_KEY, + WITH_IIQ, WITHOUT_IIQ, + NOT_YET_DEFINED, + OPT_OUT, + BLACK_LIST, + CLIENT_HINTS_KEY, + EMPTY, + VERSION +} from '../libraries/intentIqConstants/intentIqConstants.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -20,12 +36,22 @@ import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const PCID_EXPIRY = 365; const MODULE_NAME = 'intentIqId'; -export const FIRST_PARTY_KEY = '_iiq_fdata'; -export var FIRST_PARTY_DATA_KEY = '_iiq_fdata'; - -export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); +const encoderCH = { + brands: 0, + mobile: 1, + platform: 2, + architecture: 3, + bitness: 4, + model: 5, + platformVersion: 6, + wow64: 7, + fullVersionList: 8 +}; const INVALID_ID = 'INVALID_ID'; +const SUPPORTED_TYPES = ['html5', 'cookie'] + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); /** * Generate standard UUID string @@ -42,16 +68,35 @@ function generateGUID() { } /** - * Read Intent IQ data from cookie or local storage + * Encrypts plaintext. + * @param {string} plainText The plaintext to encrypt. + * @returns {string} The encrypted text as a base64 string. + */ +export function encryptData(plainText) { + return AES.encrypt(plainText, MODULE_NAME).toString(); +} + +/** + * Decrypts ciphertext. + * @param {string} encryptedText The encrypted text as a base64 string. + * @returns {string} The decrypted plaintext. + */ +export function decryptData(encryptedText) { + const bytes = AES.decrypt(encryptedText, MODULE_NAME); + return bytes.toString(Utf8); +} + +/** + * Read Intent IQ data from local storage or cookie * @param key * @return {string} */ -export function readData(key) { +export function readData(key, allowedStorage) { try { - if (storage.hasLocalStorage()) { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { return storage.getDataFromLocalStorage(key); } - if (storage.cookiesAreEnabled()) { + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { return storage.getCookie(key); } } catch (error) { @@ -60,21 +105,20 @@ export function readData(key) { } /** - * Store Intent IQ data in either cookie or local storage + * Store Intent IQ data in cookie, local storage or both of them * expiration date: 365 days * @param key * @param {string} value IntentIQ ID value to sintentIqIdSystem_spec.jstore */ -function storeData(key, value, cookieStorageEnabled = false) { +export function storeData(key, value, allowedStorage) { try { logInfo(MODULE_NAME + ': storing data: key=' + key + ' value=' + value); - if (value) { - if (storage.hasLocalStorage()) { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { storage.setDataInLocalStorage(key, value); } - const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); - if (storage.cookiesAreEnabled() && cookieStorageEnabled) { + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiresStr = (new Date(Date.now() + (PCID_EXPIRY * (60 * 60 * 24 * 1000)))).toUTCString(); storage.setCookie(key, value, expiresStr, 'LAX'); } } @@ -83,10 +127,28 @@ function storeData(key, value, cookieStorageEnabled = false) { } } +/** + * Remove Intent IQ data from cookie or local storage + * @param key + */ + +export function removeDataByKey(key, allowedStorage) { + try { + if (storage.hasLocalStorage() && allowedStorage.includes('html5')) { + storage.removeDataFromLocalStorage(key); + } + if (storage.cookiesAreEnabled() && allowedStorage.includes('cookie')) { + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(key, '', expiredDate, 'LAX'); + } + } catch (error) { + logError(error); + } +} + /** * Parse json if possible, else return null * @param data - * @param {object|null} */ function tryParse(data) { try { @@ -97,6 +159,38 @@ function tryParse(data) { } } +/** + * Processes raw client hints data into a structured format. + * @param {object} clientHints - Raw client hints data + * @return {string} A JSON string of processed client hints or an empty string if no hints + */ +export function handleClientHints(clientHints) { + const chParams = {}; + for (const key in clientHints) { + if (clientHints.hasOwnProperty(key) && clientHints[key] !== '') { + if (['brands', 'fullVersionList'].includes(key)) { + let handledParam = ''; + clientHints[key].forEach((element, index) => { + const isNotLast = index < clientHints[key].length - 1; + handledParam += `"${element.brand}";v="${element.version}"${isNotLast ? ', ' : ''}`; + }); + chParams[encoderCH[key]] = handledParam; + } else if (typeof clientHints[key] === 'boolean') { + chParams[encoderCH[key]] = `?${clientHints[key] ? 1 : 0}`; + } else { + chParams[encoderCH[key]] = `"${clientHints[key]}"`; + } + } + } + return Object.keys(chParams).length ? JSON.stringify(chParams) : ''; +} + +function defineStorageType(params) { + if (!params || !Array.isArray(params)) return ['html5']; // use locale storage be default + const filteredArr = params.filter(item => SUPPORTED_TYPES.includes(item)); + return filteredArr.length ? filteredArr : ['html5']; +} + /** @type {Submodule} */ export const intentIqIdSubmodule = { /** @@ -111,7 +205,7 @@ export const intentIqIdSubmodule = { * @returns {{intentIqId: {string}}|undefined} */ decode(value) { - return value && value != '' && INVALID_ID != value ? { 'intentIqId': value } : undefined; + return value && value != '' && INVALID_ID != value ? {'intentIqId': value} : undefined; }, /** * performs action to obtain id and return a value in the callback's response argument @@ -120,25 +214,144 @@ export const intentIqIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config) { - const configParams = (config && config.params) || {}; - if (!configParams || typeof configParams.partner !== 'number') { + const configParams = (config?.params) || {}; + let decryptedData, callbackTimeoutID; + let callbackFired = false; + let runtimeEids = { eids: [] }; + + const allowedStorage = defineStorageType(config.enabledStorageTypes); + + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, allowedStorage)); + const isGroupB = firstPartyData?.group === WITHOUT_IIQ; + + const firePartnerCallback = () => { + if (configParams.callback && !callbackFired) { + callbackFired = true; + if (callbackTimeoutID) clearTimeout(callbackTimeoutID); + if (isGroupB) runtimeEids = { eids: [] }; + configParams.callback(runtimeEids, firstPartyData?.group || NOT_YET_DEFINED); + } + } + + callbackTimeoutID = setTimeout(() => { + firePartnerCallback(); + }, configParams.timeoutInMillis || 500 + ); + + if (typeof configParams.partner !== 'number') { logError('User ID - intentIqId submodule requires a valid partner to be defined'); + firePartnerCallback() return; } - const cookieStorageEnabled = typeof configParams.enableCookieStorage === 'boolean' ? configParams.enableCookieStorage : false - if (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { FIRST_PARTY_DATA_KEY += '_' + configParams.partner; } + + const FIRST_PARTY_DATA_KEY = `_iiq_fdata_${configParams.partner}`; + let rrttStrtTime = 0; + let partnerData = {}; + let shouldCallServer = false + + const currentBrowserLowerCase = detectBrowser(); + const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; + + // Check if current browser is in blacklist + if (browserBlackList?.includes(currentBrowserLowerCase)) { + logError('User ID - intentIqId submodule: browser is in blacklist!'); + if (configParams.callback) configParams.callback('', BLACK_LIST); + return; + } + + // Get consent information + const cmpData = {}; + const uspData = uspDataHandler.getConsentData(); + const gppData = getGppValue(); + + if (uspData) { + cmpData.us_privacy = uspData; + } + + cmpData.gpp = gppData.gppString; + cmpData.gpi = gppData.gpi; + + // Read client hints from storage + let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); - // Read Intent IQ 1st party id or generate it if none exists - let firstPartyData = tryParse(readData(FIRST_PARTY_KEY)); - if (!firstPartyData || !firstPartyData.pcid || firstPartyData.pcidDate) { + // Get client hints and save to storage + if (navigator.userAgentData) { + navigator.userAgentData + .getHighEntropyValues([ + 'brands', + 'mobile', + 'bitness', + 'wow64', + 'architecture', + 'model', + 'platform', + 'platformVersion', + 'fullVersionList' + ]) + .then(ch => { + clientHints = handleClientHints(ch); + storeData(CLIENT_HINTS_KEY, clientHints, allowedStorage) + }); + } + + if (!firstPartyData?.pcid) { const firstPartyId = generateGUID(); - firstPartyData = { 'pcid': firstPartyId, 'pcidDate': Date.now() }; - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), cookieStorageEnabled); + firstPartyData = { + pcid: firstPartyId, + pcidDate: Date.now(), + group: NOT_YET_DEFINED, + cttl: 0, + uspapi_value: EMPTY, + gpp_value: EMPTY, + date: Date.now() + }; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + } else if (!firstPartyData.pcidDate) { + firstPartyData.pcidDate = Date.now(); + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); } - let partnerData = tryParse(readData(FIRST_PARTY_DATA_KEY)); - if (!partnerData) partnerData = {}; + const savedData = tryParse(readData(FIRST_PARTY_DATA_KEY, allowedStorage)) + if (savedData) { + partnerData = savedData; + + if (partnerData.wsrvcll) { + partnerData.wsrvcll = false; + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + } + } + + if (partnerData.data) { + if (partnerData.data.length) { // encrypted data + decryptedData = tryParse(decryptData(partnerData.data)); + runtimeEids = decryptedData; + } + } + + if (!firstPartyData.cttl || Date.now() - firstPartyData.date > firstPartyData.cttl || firstPartyData.uspapi_value !== cmpData.us_privacy || firstPartyData.gpp_string_value !== cmpData.gpp) { + firstPartyData.uspapi_value = cmpData.us_privacy; + firstPartyData.gpp_string_value = cmpData.gpp; + firstPartyData.isOptedOut = false + firstPartyData.cttl = 0 + shouldCallServer = true; + partnerData.data = {} + partnerData.eidl = -1 + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + } else if (firstPartyData.isOptedOut) { + firePartnerCallback() + } + + if (firstPartyData.group === WITHOUT_IIQ || (firstPartyData.group !== WITHOUT_IIQ && runtimeEids?.eids?.length)) { + firePartnerCallback() + } + + if (!shouldCallServer) { + if (isGroupB) runtimeEids = { eids: [] }; + firePartnerCallback(); + return { id: runtimeEids.eids }; + } // 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`; @@ -149,61 +362,146 @@ export const intentIqIdSubmodule = { url += (partnerData.cttl) ? '&cttl=' + encodeURIComponent(partnerData.cttl) : ''; url += (partnerData.rrtt) ? '&rrtt=' + encodeURIComponent(partnerData.rrtt) : ''; url += firstPartyData.pcidDate ? '&iiqpciddate=' + encodeURIComponent(firstPartyData.pcidDate) : ''; + url += cmpData.us_privacy ? '&pa=' + encodeURIComponent(cmpData.us_privacy) : ''; + url += cmpData.gpp ? '&gpp=' + encodeURIComponent(cmpData.gpp) : ''; + url += cmpData.gpi ? '&gpi=' + cmpData.gpi : ''; + url += clientHints ? '&uh=' + encodeURIComponent(clientHints) : ''; + url += VERSION ? '&jsver=' + VERSION : ''; + url += firstPartyData?.group ? '&testGroup=' + encodeURIComponent(firstPartyData.group) : ''; + + // Add vrref and fui to the URL + url = appendVrrefAndFui(url, configParams.domainName); + + const storeFirstPartyData = () => { + partnerData.eidl = runtimeEids?.eids?.length || -1 + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + } 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 - let shouldUpdateLs = false; - if ('pid' in respJson) { - firstPartyData.pid = respJson.pid; - shouldUpdateLs = true; + if (respJson) { + partnerData.date = Date.now(); + firstPartyData.date = Date.now(); + const defineEmptyDataAndFireCallback = () => { + respJson.data = partnerData.data = runtimeEids = { eids: [] }; + storeFirstPartyData() + firePartnerCallback() + callback(runtimeEids) } + if (callbackTimeoutID) clearTimeout(callbackTimeoutID) if ('cttl' in respJson) { - partnerData.cttl = respJson.cttl; - shouldUpdateLs = true; + firstPartyData.cttl = respJson.cttl; + } else firstPartyData.cttl = 86400000; + + if ('tc' in respJson) { + partnerData.terminationCause = respJson.tc; + if (respJson.tc == 41) { + firstPartyData.group = WITHOUT_IIQ; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + defineEmptyDataAndFireCallback(); + return + } else { + firstPartyData.group = WITH_IIQ; + } } - // If should save and data is empty, means we should save as INVALID_ID - if (respJson.data == '') { - respJson.data = INVALID_ID; - } else { + if ('isOptedOut' in respJson) { + if (respJson.isOptedOut !== firstPartyData.isOptedOut) { + firstPartyData.isOptedOut = respJson.isOptedOut; + } + if (respJson.isOptedOut === true) { + firstPartyData.group = OPT_OUT; + storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), allowedStorage); + defineEmptyDataAndFireCallback() + return + } + } + if ('pid' in respJson) { + firstPartyData.pid = respJson.pid; + } + if ('ls' in respJson) { + if (respJson.ls === false) { + defineEmptyDataAndFireCallback() + return + } + // If data is empty, means we should save as INVALID_ID + if (respJson.data == '') { + respJson.data = INVALID_ID; + } else { + // If data is a single string, assume it is an id with source intentiq.com + if (respJson.data && typeof respJson.data === 'string') { + respJson.data = {eids: [respJson.data]} + } + } partnerData.data = respJson.data; - shouldUpdateLs = true; } + + if ('ct' in respJson) { + partnerData.ct = respJson.ct; + } + + if ('sid' in respJson) { + partnerData.siteId = respJson.sid; + } + if (rrttStrtTime && rrttStrtTime > 0) { partnerData.rrtt = Date.now() - rrttStrtTime; - shouldUpdateLs = true; } - if (shouldUpdateLs === true) { - partnerData.date = Date.now(); - storeData(FIRST_PARTY_KEY, JSON.stringify(firstPartyData), cookieStorageEnabled); - storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), cookieStorageEnabled); + + if (respJson.data?.eids) { + runtimeEids = respJson.data + callback(respJson.data.eids); + firePartnerCallback() + const encryptedData = encryptData(JSON.stringify(respJson.data)) + partnerData.data = encryptedData; + } else { + callback(runtimeEids); + firePartnerCallback() } - callback(respJson.data); + storeFirstPartyData(); } else { - callback(); + callback(runtimeEids); + firePartnerCallback() } }, error: error => { logError(MODULE_NAME + ': ID fetch encountered an error', error); - callback(); + callback(runtimeEids); } }; - if (partnerData.date && partnerData.cttl && partnerData.data && - Date.now() - partnerData.date < partnerData.cttl) { callback(partnerData.data); } else { - rrttStrtTime = Date.now(); - ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); - } + rrttStrtTime = Date.now(); + + partnerData.wsrvcll = true; + storeData(FIRST_PARTY_DATA_KEY, JSON.stringify(partnerData), allowedStorage); + ajax(url, callbacks, undefined, {method: 'GET', withCredentials: true}); }; - return { callback: resp }; + const respObj = {callback: resp}; + + if (runtimeEids?.eids?.length) respObj.id = runtimeEids.eids; + return respObj }, eids: { 'intentIqId': { source: 'intentiq.com', - atype: 1 + atype: 1, + getSource: function (data) { + return data.source; + }, + getValue: function (data) { + if (data?.uids?.length) { + return data.uids[0].id + } + return null + }, + getUidExt: function (data) { + if (data?.uids?.length) { + return data.uids[0].ext; + } + return null + } }, } }; diff --git a/modules/intentIqIdSystem.md b/modules/intentIqIdSystem.md new file mode 100644 index 00000000000..34fd495d625 --- /dev/null +++ b/modules/intentIqIdSystem.md @@ -0,0 +1,71 @@ +``` +Module Name: IntentIQ Id System +Module Type: Id System +Maintainer: julian@intentiq.com, dmytro.piskun@intentiq.com +usp_supported: true +gpp_sids: usnat +``` + +# Intent IQ Universal ID module + +By leveraging the Intent IQ identity graph, our module helps publishers, SSPs, and DSPs overcome the challenges of monetizing cookie-less inventory and preparing for a future without 3rd-party cookies. Our solution implements 1st-party data clustering and provides Intent IQ person IDs with over 90% coverage and unmatched accuracy in supported countries while remaining privacy-friendly and CCPA compliant. This results in increased CPMs, higher fill rates, and, ultimately, lifting overall revenue + +# All you need is a few basic steps to start using our solution. + +## Registration + +Navigate to [our portal ](https://www.intentiq.com/) and contact our team for partner ID. +check our [documentation](https://pbmodule.documents.intentiq.com/) to get more information about our solution and how utilze it's full potential + +## Integration + +``` +gulp build –modules=intentIqIdSystem +``` + +We recommend including the Intent IQ Analytics adapter module for improved visibility + +## Configuration + +### Parameters + +Please find below list of paramters that could be used in configuring Intent IQ Universal ID module + +| Param under userSync.userIds[] | Scope | Type | Description | Example | +| ------------------------------ | -------- |----------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------------------------------------------| +| name | Required | String | The name of this module: "intentIqId" | `"intentIqId"` | +| params | Required | Object | Details for IntentIqId initialization. | | +| params.partner | Required | Number | This is the partner ID value obtained from registering with IntentIQ. | `1177538` | +| params.pcid | Optional | String | This is the partner cookie ID, it is a dynamic value attached to the request. | `"g3hC52b"` | +| params.pai | Optional | String | This is the partner customer ID / advertiser ID, it is a dynamic value attached to the request. | `"advertiser1"` | +| params.callback | Required | Function | This is a callback which is trigered with data and AB group | `(data, group) => console.log({ data, group })` | +| params.timeoutInMillis | Optional | Number | This is the timeout in milliseconds, which defines the maximum duration before the callback is triggered. The default value is 500. | `450` | +| params.browserBlackList | Optional |  String | This is the name of a browser that can be added to a blacklist. | `"chrome"` | +| params.manualWinReportEnabled | Optional | Boolean | This variable determines whether the bidWon event is triggered automatically. If set to false, the event will occur automatically, and manual reporting with reportExternalWin will be disabled. If set to true, the event will not occur automatically, allowing manual reporting through reportExternalWin. The default value is false. | `true`| +| params.domainName | Optional | String | Specifies the domain of the page in which the IntentIQ object is currently running and serving the impression. This domain will be used later in the revenue reporting breakdown by domain. For example, cnn.com. It identifies the primary source of requests to the IntentIQ servers, even within nested web pages. | `"currentDomain.com"` | + +### Configuration example + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: "intentIqId", + params: { + partner: 123456, // valid partner id + timeoutInMillis: 500, + browserBlackList: "chrome", + callback: (data, group) => window.pbjs.requestBids(), + manualWinReportEnabled: true, + domainName: "currentDomain.com" + }, + storage: { + type: "html5", + name: "intentIqId", // set localstorage with this name + expires: 0, + refreshInSeconds: 0 + } + }] + } +}); +``` diff --git a/modules/interactiveOffersBidAdapter.js b/modules/interactiveOffersBidAdapter.js index feb576fbb02..bb27239fef4 100644 --- a/modules/interactiveOffersBidAdapter.js +++ b/modules/interactiveOffersBidAdapter.js @@ -1,4 +1,4 @@ -import {isNumber, logWarn} from '../src/utils.js'; +import {deepClone, isNumber, logWarn} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; @@ -79,8 +79,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { // TODO: these should probably look at refererInfo 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'])); + let openRTBRequest = deepClone(DEFAULT['OpenRTBBidRequest']); openRTBRequest.id = bidderRequest.bidderRequestId; openRTBRequest.ext = { // TODO: please do not send internal data structures over the network @@ -89,38 +88,38 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { auctionId: prebidRequest.auctionId }; - openRTBRequest.site = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSite'])); + openRTBRequest.site = deepClone(DEFAULT['OpenRTBBidRequestSite']); openRTBRequest.site.id = domain; openRTBRequest.site.name = domain; openRTBRequest.site.domain = domain; openRTBRequest.site.page = pageURL; openRTBRequest.site.ref = prebidRequest.refererInfo.ref; - openRTBRequest.site.publisher = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSitePublisher'])); + openRTBRequest.site.publisher = deepClone(DEFAULT['OpenRTBBidRequestSitePublisher']); openRTBRequest.site.publisher.id = 0; openRTBRequest.site.publisher.name = prebidRequest.refererInfo.domain; openRTBRequest.site.publisher.domain = domain; openRTBRequest.site.publisher.domain = domain; - openRTBRequest.site.content = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSiteContent'])); + openRTBRequest.site.content = deepClone(DEFAULT['OpenRTBBidRequestSiteContent']); - openRTBRequest.source = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestSource'])); + openRTBRequest.source = deepClone(DEFAULT['OpenRTBBidRequestSource']); openRTBRequest.source.fd = 0; openRTBRequest.source.tid = prebidRequest.ortb2?.source?.tid; openRTBRequest.source.pchain = ''; - openRTBRequest.device = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestDevice'])); + openRTBRequest.device = deepClone(DEFAULT['OpenRTBBidRequestDevice']); - openRTBRequest.user = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestUser'])); + openRTBRequest.user = deepClone(DEFAULT['OpenRTBBidRequestUser']); openRTBRequest.imp = []; prebidRequest.bids.forEach(function(bid) { if (!ret.partnerId) { ret.partnerId = bid.params.partnerId; } - let imp = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImp'])); + let imp = deepClone(DEFAULT['OpenRTBBidRequestImp']); imp.id = bid.bidId; - imp.secure = secure; + imp.secure = bid.ortb2Imp?.secure ?? 1; imp.tagid = bid.adUnitCode; imp.ext = { rawdata: bid @@ -131,7 +130,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { Object.keys(bid.mediaTypes).forEach(function(mediaType) { if (mediaType == 'banner') { - imp.banner = JSON.parse(JSON.stringify(DEFAULT['OpenRTBBidRequestImpBanner'])); + imp.banner = deepClone(DEFAULT['OpenRTBBidRequestImpBanner']); imp.banner.w = 0; imp.banner.h = 0; imp.banner.format = []; @@ -156,7 +155,7 @@ function parseResponseOpenRTBToPrebidjs(openRTBResponse) { response.seatbid.forEach(function(seatbid) { if (seatbid.bid && seatbid.bid.forEach) { seatbid.bid.forEach(function(bid) { - let prebid = JSON.parse(JSON.stringify(DEFAULT['PrebidBid'])); + let prebid = deepClone(DEFAULT['PrebidBid']); prebid.requestId = bid.impid; prebid.ad = bid.adm; prebid.creativeId = bid.crid; diff --git a/modules/invamiaBidAdapter.js b/modules/invamiaBidAdapter.js index 96af163ca4f..7f9a40e1473 100644 --- a/modules/invamiaBidAdapter.js +++ b/modules/invamiaBidAdapter.js @@ -1,5 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; +import { buildBannerRequests, interpretBannerResponse } from '../libraries/biddoInvamiaUtils/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,86 +13,15 @@ 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) { + 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; + buildRequests: function (validBidRequests) { + return validBidRequests.flatMap((bidRequest) => buildBannerRequests(bidRequest, ENDPOINT_URL)); }, - /** - * 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; + interpretResponse: function (serverResponse, { bidderRequest }) { + return interpretBannerResponse(serverResponse, bidderRequest); }, -} +}; registerBidder(spec); diff --git a/modules/invibesBidAdapter.js b/modules/invibesBidAdapter.js index 7ba2b8225b0..35c1a12ff5e 100644 --- a/modules/invibesBidAdapter.js +++ b/modules/invibesBidAdapter.js @@ -14,7 +14,7 @@ const CONSTANTS = { SYNC_ENDPOINT: 'https://k.r66net.com/GetUserSync', TIME_TO_LIVE: 300, DEFAULT_CURRENCY: 'EUR', - PREBID_VERSION: 12, + PREBID_VERSION: 13, METHOD: 'GET', INVIBES_VENDOR_ID: 436, USERID_PROVIDERS: ['pubcid', 'pubProvidedId', 'uid2', 'zeotapIdPlus', 'id5id'], @@ -22,7 +22,7 @@ const CONSTANTS = { DISABLE_USER_SYNC: true }; -const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); +export const storage = getStorageManager({bidderCode: CONSTANTS.BIDDER_CODE}); export const spec = { code: CONSTANTS.BIDDER_CODE, @@ -140,7 +140,7 @@ function buildRequest(bidRequests, bidderRequest) { _userId = _userId || bidRequest.userId; }); - invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent); + invibes.optIn = invibes.optIn || readGdprConsent(bidderRequest.gdprConsent, bidderRequest.uspConsent); invibes.visitId = invibes.visitId || generateRandomId(); @@ -176,6 +176,7 @@ function buildRequest(bidRequests, bidderRequest) { li: invibes.legitimateInterests.toString(), tc: invibes.gdpr_consent, + uspc: bidderRequest.uspConsent, isLocalStorageEnabled: storage.hasLocalStorage(), preventPageViewEvent: preventPageViewEvent, isPlacementRefresh: isPlacementRefresh, @@ -498,7 +499,7 @@ function renderCreative(bidModel) { } function readFromLocalStorage(key) { - if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { + if ((invibes.GdprModuleInstalled || invibes.UspModuleInstalled) && (!invibes.optIn || !invibes.purposes[0])) { return; } @@ -592,20 +593,15 @@ function buildSyncUrl() { return syncUrl; } -function readGdprConsent(gdprConsent) { +function readGdprConsent(gdprConsent, usConsent) { + invibes.GdprModuleInstalled = false; + invibes.UspModuleInstalled = false; if (gdprConsent && gdprConsent.vendorData) { invibes.GdprModuleInstalled = true; 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; - } + setAllPurposesAndLegitimateInterests(true); return 2; } @@ -635,12 +631,29 @@ function readGdprConsent(gdprConsent) { } return 2; + } else if (usConsent && usConsent.length > 2) { + invibes.UspModuleInstalled = true; + if (usConsent[2] == 'N') { + setAllPurposesAndLegitimateInterests(true); + return 2; + } } - invibes.GdprModuleInstalled = false; + setAllPurposesAndLegitimateInterests(false); return 0; } +function setAllPurposesAndLegitimateInterests(value) { + var index; + for (index = 0; index < invibes.purposes.length; ++index) { + invibes.purposes[index] = value; + } + + for (index = 0; index < invibes.legitimateInterests.length; ++index) { + invibes.legitimateInterests[index] = value; + } +} + function tryCopyValueToArray(value, target, length) { if (value instanceof Array) { for (let i = 0; i < length && i < value.length; i++) { @@ -751,7 +764,7 @@ invibes.getCookie = function (name) { return; } - if (invibes.GdprModuleInstalled && (!invibes.optIn || !invibes.purposes[0])) { + if ((invibes.GdprModuleInstalled || invibes.UspModuleInstalled) && (!invibes.optIn || !invibes.purposes[0])) { return; } diff --git a/modules/invisiblyAnalyticsAdapter.js b/modules/invisiblyAnalyticsAdapter.js index 24c2c452402..af21b1470f6 100644 --- a/modules/invisiblyAnalyticsAdapter.js +++ b/modules/invisiblyAnalyticsAdapter.js @@ -5,7 +5,7 @@ import { ajaxBuilder } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; -import { generateUUID, logInfo } from '../src/utils.js'; +import { deepClone, hasNonSerializableProperty, generateUUID, logInfo } from '../src/utils.js'; import { EVENTS } from '../src/constants.js'; const DEFAULT_EVENT_URL = 'https://api.pymx5.com/v1/' + 'sites/events'; @@ -133,7 +133,12 @@ function flush() { } function handleEvent(eventType, eventArgs) { - eventArgs = eventArgs ? JSON.parse(JSON.stringify(eventArgs)) : {}; + if (eventArgs) { + eventArgs = hasNonSerializableProperty(eventArgs) ? eventArgs : deepClone(eventArgs) + } else { + eventArgs = {} + } + let invisiblyEvent = {}; switch (eventType) { diff --git a/modules/iqmBidAdapter.js b/modules/iqmBidAdapter.js deleted file mode 100644 index c94a88748a7..00000000000 --- a/modules/iqmBidAdapter.js +++ /dev/null @@ -1,277 +0,0 @@ -import {_each, deepAccess, getBidIdParameter, isArray} from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {INSTREAM} from '../src/video.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - */ - -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 = deepAccess(bid, 'mediaTypes.banner'); - const videoMediaType = deepAccess(bid, 'mediaTypes.video'); - const context = deepAccess(bid, 'mediaTypes.video.context'); - if ((videoMediaType && context === INSTREAM)) { - const videoBidderParams = 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 = getBidIdParameter('bidfloor', bid.params); - - const imp = { - id: bid.bidId, - secure: 1, - bidfloor: bidfloor || 0, - displaymanager: 'Prebid.js', - displaymanagerver: VERSION, - - } - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = getSize(bid.sizes); - imp.mediatype = 'banner'; - } else if (deepAccess(bid, 'mediaTypes.video')) { - imp.video = _buildVideoORTB(bid); - imp.mediatype = 'video'; - } - const site = getSite(bidderRequest); - let device = getDevice(bid.params); - finalRequest = { - sizes: bid.sizes, - id: bid.bidId, - publisherId: getBidIdParameter('publisherId', bid.params), - placementId: getBidIdParameter('placementId', bid.params), - device: device, - site: site, - imp: imp, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bid.auctionId, - adUnitCode: bid.adUnitCode, - bidderRequestId: bid.bidderRequestId, - uuid: bid.bidId, - // TODO: please do not send internal data structures over the network - // I am not going to attempt to accommodate this, no way this is usable on their end, it changes way too frequently - 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 && isArray(serverResponse.seatbid)) { - _each(serverResponse.seatbid, function (bidList) { - _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 || 60 - }; - - 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; - - // TODO: are these the right refererInfo values? - domain = refererInfo.domain; - page = refererInfo.page; - referrer = refererInfo.ref; - - return { - domain, - page, - Id, - referrer - }; -}; - -function _buildVideoORTB(bidRequest) { - const videoAdUnit = deepAccess(bidRequest, 'mediaTypes.video'); - const videoBidderParams = 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/iqxBidAdapter.js b/modules/iqxBidAdapter.js index 1bef158c4a2..e859dfd2c01 100644 --- a/modules/iqxBidAdapter.js +++ b/modules/iqxBidAdapter.js @@ -1,205 +1,16 @@ -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -const CUR = 'USD'; const BIDDER_CODE = 'iqx'; const ENDPOINT = 'https://pbjs.iqzonertb.live'; -/** - * 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. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { - logError('Env or pid is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - request.auctionId = req.ortb2?.source?.tid; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - pid: req.params.pid - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT + '/bid', - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - 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. - */ -function interpretResponse(serverResponse, {bidderRequest}) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * 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. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [type, url] = pixel; - const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, aliases: ['iqx'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/iqzoneBidAdapter.js b/modules/iqzoneBidAdapter.js index 52f3be7e4b4..9603a509ac5 100644 --- a/modules/iqzoneBidAdapter.js +++ b/modules/iqzoneBidAdapter.js @@ -1,211 +1,19 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'iqzone'; const AD_URL = 'https://smartssp-us-east.iqzone.com/pbjs'; -const SYNC_URL = 'https://cs.smartssp.iqzone.com'; - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const SYNC_URL = 'https://cs.iqzone.com'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/ixBidAdapter.js b/modules/ixBidAdapter.js index f56e2790ad6..0ec60b51ca2 100644 --- a/modules/ixBidAdapter.js +++ b/modules/ixBidAdapter.js @@ -26,7 +26,6 @@ import { Renderer } from '../src/Renderer.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'ix'; -const ALIAS_BIDDER_CODE = 'roundel'; const GLOBAL_VENDOR_ID = 10; const SECURE_BID_URL = 'https://htlb.casalemedia.com/openrtb/pbjs'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE]; @@ -59,22 +58,9 @@ const SOURCE_RTI_MAPPING = { 'neustar.biz': 'fabrickId', 'zeotap.com': 'zeotapIdPlus', 'uidapi.com': 'UID2', - 'adserver.org': 'TDID', - 'id5-sync.com': '', // ID5 Universal ID, configured as id5Id - 'crwdcntrl.net': '', // Lotame Panorama ID, lotamePanoramaId - 'epsilon.com': '', // Publisher Link, publinkId - 'audigent.com': '', // Hadron ID from Audigent, hadronId - 'pubcid.org': '', // SharedID, pubcid - 'utiq.com': '', // Utiq - 'criteo.com': '', // Criteo - 'euid.eu': '', // EUID - 'intimatemerger.com': '', - '33across.com': '', - 'liveintent.indexexchange.com': '', - 'google.com': '' + 'adserver.org': 'TDID' }; const PROVIDERS = [ - 'britepoolid', 'lipbid', 'criteoId', 'merkleId', @@ -650,10 +636,9 @@ function getEidInfo(allEids) { if (isArray(allEids)) { for (const eid of allEids) { const isSourceMapped = SOURCE_RTI_MAPPING.hasOwnProperty(eid.source); - const allowAllEidsFeatureEnabled = FEATURE_TOGGLES.isFeatureEnabled('pbjs_allow_all_eids'); const hasUids = deepAccess(eid, 'uids.0'); - if ((isSourceMapped || allowAllEidsFeatureEnabled) && hasUids) { + if (hasUids) { seenSources[eid.source] = true; if (isSourceMapped && SOURCE_RTI_MAPPING[eid.source] !== '') { @@ -661,7 +646,6 @@ function getEidInfo(allEids) { rtiPartner: SOURCE_RTI_MAPPING[eid.source] }; } - delete eid.uids[0].atype; toSend.push(eid); if (toSend.length >= MAX_EID_SOURCES) { break; @@ -696,11 +680,6 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { addRTI(userEids, eidInfo); } - // If `roundel` alias bidder, only send requests if liveramp ids exist. - if (bidderRequest && bidderRequest.bidderCode === ALIAS_BIDDER_CODE && !eidInfo.seenSources['liveramp.com']) { - return []; - } - const requests = []; let r = createRequest(validBidRequests); @@ -708,7 +687,7 @@ function buildRequest(validBidRequests, bidderRequest, impressions, version) { r = addRequestedFeatureToggles(r, FEATURE_TOGGLES.REQUESTED_FEATURE_TOGGLES) // getting ixdiags for adunits of the video, outstream & multi format (MF) style - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') let ixdiag = buildIXDiag(validBidRequests, fledgeEnabled); for (let key in ixdiag) { r.ext.ixdiag[key] = ixdiag[key]; @@ -974,6 +953,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { const tid = impressions[impKeys[adUnitIndex]].tid; const sid = impressions[impKeys[adUnitIndex]].sid; const auctionEnvironment = impressions[impKeys[adUnitIndex]].ae; + const paapi = impressions[impKeys[adUnitIndex]].paapi; const bannerImpressions = impressionObjects.filter(impression => BANNER in impression); const otherImpressions = impressionObjects.filter(impression => !(BANNER in impression)); @@ -1023,7 +1003,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { _bannerImpression.banner.pos = position; } - if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID) { + if (dfpAdUnitCode || gpid || tid || sid || auctionEnvironment || externalID || paapi) { _bannerImpression.ext = {}; _bannerImpression.ext.dfp_ad_unit_code = dfpAdUnitCode; @@ -1035,6 +1015,7 @@ function addImpressions(impressions, impKeys, r, adUnitIndex) { // enable fledge auction if (auctionEnvironment == 1) { _bannerImpression.ext.ae = 1; + _bannerImpression.ext.paapi = paapi; } } @@ -1437,17 +1418,19 @@ function createBannerImps(validBidRequest, missingBannerSizes, bannerImps, bidde bannerImps[validBidRequest.adUnitCode].pos = deepAccess(validBidRequest, 'mediaTypes.banner.pos'); // Add Fledge flag if enabled - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const auctionEnvironment = deepAccess(validBidRequest, 'ortb2Imp.ext.ae') + const paapi = deepAccess(validBidRequest, 'ortb2Imp.ext.paapi') + if (paapi) { + bannerImps[validBidRequest.adUnitCode].paapi = paapi + } if (auctionEnvironment) { if (isInteger(auctionEnvironment)) { bannerImps[validBidRequest.adUnitCode].ae = auctionEnvironment; } else { logWarn('error setting auction environment flag - must be an integer') } - } else if (deepAccess(bidderRequest, 'defaultForSlots') == 1) { - bannerImps[validBidRequest.adUnitCode].ae = 1 } } @@ -1603,11 +1586,6 @@ export const spec = { code: BIDDER_CODE, gvlid: GLOBAL_VENDOR_ID, - aliases: [{ - code: ALIAS_BIDDER_CODE, - gvlid: GLOBAL_VENDOR_ID, - skipPbsAliasing: false - }], supportedMediaTypes: SUPPORTED_AD_TYPES, /** @@ -1859,7 +1837,7 @@ export const spec = { try { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } catch (error) { logWarn('Error attaching AuctionConfigs', error); diff --git a/modules/ixBidAdapter.md b/modules/ixBidAdapter.md index 0705c5932cf..f2f6d97daf9 100644 --- a/modules/ixBidAdapter.md +++ b/modules/ixBidAdapter.md @@ -472,7 +472,7 @@ The timeout value must be a positive whole number in milliseconds. Protected Audience API (FLEDGE) =========================== -In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) module to build and enable Fledge. +In order to enable receiving [Protected Audience API](https://developer.chrome.com/en/docs/privacy-sandbox/fledge/) traffic, follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module to build and enable Fledge. Additional Information ====================== diff --git a/modules/justIdSystem.js b/modules/justIdSystem.js index a5698023020..e4f1828c573 100644 --- a/modules/justIdSystem.js +++ b/modules/justIdSystem.js @@ -9,6 +9,7 @@ import * as utils from '../src/utils.js' import { submodule } from '../src/hook.js' import { loadExternalScript } from '../src/adloader.js' import {includes} from '../src/polyfill.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -154,7 +155,7 @@ const CombinedUidProvider = function(configWrapper, consentData, cacheIdObj) { const url = configWrapper.getUrl(); this.getUid = function(idCallback, errCallback) { - const scriptTag = loadExternalScript(url, EXTERNAL_SCRIPT_MODULE_CODE, () => { + const scriptTag = loadExternalScript(url, MODULE_TYPE_UID, EXTERNAL_SCRIPT_MODULE_CODE, () => { utils.logInfo(LOG_PREFIX, 'script loaded', url); const eventDetails = { diff --git a/modules/jwplayerBidAdapter.js b/modules/jwplayerBidAdapter.js index 151d08bf3a6..c58eed8ffb8 100644 --- a/modules/jwplayerBidAdapter.js +++ b/modules/jwplayerBidAdapter.js @@ -2,7 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO } from '../src/mediaTypes.js'; import { isArray, isFn, deepAccess, deepSetValue, getDNT, logError, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; const BIDDER_CODE = 'jwplayer'; const BASE_URL = 'https://vpb-server.jwplayer.com/'; diff --git a/modules/jwplayerRtdProvider.js b/modules/jwplayerRtdProvider.js index 29ce0da5317..78c26cbda6a 100644 --- a/modules/jwplayerRtdProvider.js +++ b/modules/jwplayerRtdProvider.js @@ -12,7 +12,7 @@ import {submodule} from '../src/hook.js'; import {config} from '../src/config.js'; import {ajaxBuilder} from '../src/ajax.js'; -import {deepAccess, logError} from '../src/utils.js'; +import { deepAccess, logError, logWarn } from '../src/utils.js' import {find} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; @@ -31,9 +31,7 @@ const playlistItemCache = {}; const pendingRequests = {}; let activeRequestCount = 0; let resumeBidRequest; -// defaults to 'always' for backwards compatibility -// TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY -let overrideContentId = ENRICH_ALWAYS; +let overrideContentId = ENRICH_WHEN_EMPTY; let overrideContentUrl = ENRICH_WHEN_EMPTY; let overrideContentTitle = ENRICH_WHEN_EMPTY; let overrideContentDescription = ENRICH_WHEN_EMPTY; @@ -83,9 +81,7 @@ export function fetchTargetingInformation(jwTargeting) { } export function setOverrides(params) { - // For backwards compatibility, default to always unless overridden by Publisher. - // TODO: Prebid 9 - replace with ENRICH_WHEN_EMPTY - overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_ALWAYS); + overrideContentId = sanitizeOverrideParam(params.overrideContentId, ENRICH_WHEN_EMPTY); overrideContentUrl = sanitizeOverrideParam(params.overrideContentUrl, ENRICH_WHEN_EMPTY); overrideContentTitle = sanitizeOverrideParam(params.overrideContentTitle, ENRICH_WHEN_EMPTY); overrideContentDescription = sanitizeOverrideParam(params.overrideContentDescription, ENRICH_WHEN_EMPTY); @@ -433,17 +429,37 @@ export function addTargetingToBid(bid, targeting) { bid.rtd = Object.assign({}, rtd, jwRtd); } -function getPlayer(playerDivId) { +export function getPlayer(playerDivId) { const jwplayer = window.jwplayer; if (!jwplayer) { logError(SUBMODULE_NAME + '.js was not found on page'); return; } - const player = jwplayer(playerDivId); - if (!player || !player.getPlaylist) { - logError('player ID did not match any players'); + let player = jwplayer(playerDivId); + if (player && player.getPlaylist) { + return player; + } + + const playerOnPageCount = document.getElementsByClassName('jwplayer').length; + if (playerOnPageCount === 0) { + logError('No JWPlayer instances have been detected on the page'); return; } - return player; + + let errorMessage = `player Div ID ${playerDivId} did not match any players.`; + + // If there are multiple instances on the page, we cannot guess which one should be targeted. + if (playerOnPageCount > 1) { + logError(errorMessage); + return; + } + + player = jwplayer(); + if (player && player.getPlaylist) { + logWarn(`${errorMessage} Targeting player Div ID ${player.id} instead`); + return player; + } + + logError(errorMessage); } diff --git a/modules/jwplayerRtdProvider.md b/modules/jwplayerRtdProvider.md index 936cd1d10a2..44d696eea6d 100644 --- a/modules/jwplayerRtdProvider.md +++ b/modules/jwplayerRtdProvider.md @@ -12,16 +12,20 @@ Publishers must register JW Player as a real time data provider by setting up a following structure: ```javascript -const jwplayerDataProvider = { - name: "jwplayer" -}; - pbjs.setConfig({ ..., realTimeData: { - dataProviders: [ - jwplayerDataProvider - ] + dataProviders: [{ + name: 'jwplayer', + waitForIt: true, + params: { + mediaIDs: ['abc', 'def', 'ghi', 'jkl'], + overrideContentId: 'always', + overrideContentUrl: 'always', + overrideContentTitle: 'always', + overrideContentDescription: 'always' + } + }] } }); ``` @@ -86,7 +90,7 @@ realTimeData = { | waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Defaults to false | | params | Object | | | | params.mediaIDs | Array of Strings | Media Ids for prefetching | Optional | -| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'always' | +| params.overrideContentId | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.id | Defaults to 'whenEmpty' | | params.overrideContentUrl | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.url | Defaults to 'whenEmpty' | | params.overrideContentTitle | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.title | Defaults to 'whenEmpty' | | params.overrideContentDescription | String enum: 'always', 'whenEmpty' or 'never' | Determines when the module should update the oRTB site.content.ext.description | Defaults to 'whenEmpty' | @@ -155,7 +159,7 @@ To view an example: - in your browser, navigate to: -`http://localhost:9999/integrationExamples/gpt/jwplayerRtdProvider_example.html` +`http://localhost:9999/integrationExamples/realTimeData/jwplayerRtdProvider_example.html` **Note:** the mediaIds in the example are placeholder values; replace them with your existing IDs. diff --git a/modules/jwplayerVideoProvider.js b/modules/jwplayerVideoProvider.js index ed6b69d756a..54de1949e6f 100644 --- a/modules/jwplayerVideoProvider.js +++ b/modules/jwplayerVideoProvider.js @@ -12,6 +12,12 @@ import stateFactory from '../libraries/video/shared/state.js'; import { JWPLAYER_VENDOR } from '../libraries/video/constants/vendorCodes.js'; import { getEventHandler } from '../libraries/video/shared/eventHandler.js'; import { submodule } from '../src/hook.js'; +/** + * @typedef {import('../libraries/video/constants/ortb.js').OrtbVideoParams} OrtbVideoParams + * @typedef {import('../libraries/video/shared/state.js').State} State + * @typedef {import('../modules/videoModule/coreVideo.js').VideoProvider} VideoProvider + * @typedef {import('../modules/videoModule/coreVideo.js').videoProviderConfig} videoProviderConfig + */ /** * @constructor diff --git a/modules/kargoBidAdapter.js b/modules/kargoBidAdapter.js index cafbbd982fa..9416e6a0411 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.js @@ -1,4 +1,4 @@ -import { _each, isEmpty, buildUrl, deepAccess, pick, triggerPixel, logError } from '../src/utils.js'; +import { _each, isEmpty, buildUrl, deepAccess, pick, logError, isPlainObject } from '../src/utils.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -48,7 +48,7 @@ const SUA_ATTRIBUTES = [ const CERBERUS = Object.freeze({ KEY: 'krg_crb', - SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&idx={INDEX}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', + SYNC_URL: 'https://crb.kargo.com/api/v1/initsyncrnd/{UUID}?seed={SEED}&gdpr={GDPR}&gdpr_consent={GDPR_CONSENT}&us_privacy={US_PRIVACY}&gpp={GPP_STRING}&gpp_sid={GPP_SID}', SYNC_COUNT: 5, PAGE_VIEW_ID: 'pageViewId', PAGE_VIEW_TIMESTAMP: 'pageViewTimestamp', @@ -95,16 +95,13 @@ function buildRequests(validBidRequests, bidderRequest) { ] }, imp: impressions, - user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent) + user: getUserIds(tdidAdapter, bidderRequest.uspConsent, bidderRequest.gdprConsent, firstBidRequest.userIdAsEids, bidderRequest.gppConsent), + ext: getExtensions(firstBidRequest.ortb2, bidderRequest?.refererInfo) }); - // Add full ortb2 object as backup - if (firstBidRequest.ortb2) { - const siteCat = firstBidRequest.ortb2.site?.cat; - if (siteCat != null) { - krakenParams.site = { cat: siteCat }; - } - krakenParams.ext = { ortb2: firstBidRequest.ortb2 }; + // Add site.cat if it exists + if (firstBidRequest.ortb2?.site?.cat != null) { + krakenParams.site = { cat: firstBidRequest.ortb2.site.cat }; } // Add schain @@ -186,6 +183,10 @@ function buildRequests(validBidRequests, bidderRequest) { krakenParams.page = page; } + if (krakenParams.ext && Object.keys(krakenParams.ext).length === 0) { + delete krakenParams.ext; + } + return Object.assign({}, bidderRequest, { method: BIDDER.REQUEST_METHOD, url: `https://${BIDDER.HOST}${BIDDER.REQUEST_ENDPOINT}`, @@ -250,7 +251,7 @@ function interpretResponse(response, bidRequest) { if (fledgeAuctionConfigs.length > 0) { return { bids: bidResponses, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs } } else { return bidResponses; @@ -273,19 +274,16 @@ function getUserSyncs(syncOptions, _, gdprConsent, usPrivacy, gppConsent) { return syncs; } if (syncOptions.iframeEnabled && seed && clientId) { - for (let i = 0; i < CERBERUS.SYNC_COUNT; i++) { - syncs.push({ - type: 'iframe', - url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) - .replace('{SEED}', seed) - .replace('{INDEX}', i) - .replace('{GDPR}', gdpr) - .replace('{GDPR_CONSENT}', gdprConsentString) - .replace('{US_PRIVACY}', usPrivacy || '') - .replace('{GPP_STRING}', gppString) - .replace('{GPP_SID}', gppApplicableSections) - }); - } + syncs.push({ + type: 'iframe', + url: CERBERUS.SYNC_URL.replace('{UUID}', clientId) + .replace('{SEED}', seed) + .replace('{GDPR}', gdpr) + .replace('{GDPR_CONSENT}', gdprConsentString) + .replace('{US_PRIVACY}', usPrivacy || '') + .replace('{GPP_STRING}', gppString) + .replace('{GPP_SID}', gppApplicableSections) + }) } return syncs; } @@ -300,6 +298,13 @@ function onTimeout(timeoutData) { }); } +function getExtensions(ortb2, refererInfo) { + const ext = {}; + if (ortb2) ext.ortb2 = ortb2; + if (refererInfo) ext.refererInfo = refererInfo; + return ext; +} + function _generateRandomUUID() { try { // crypto.getRandomValues is supported everywhere but Opera Mini for years @@ -447,21 +452,20 @@ function getRequestCount() { } function sendTimeoutData(auctionId, auctionTimeout) { - let params = { - aid: auctionId, - ato: auctionTimeout - }; - - try { - let timeoutRequestUrl = buildUrl({ - protocol: 'https', - hostname: BIDDER.HOST, - pathname: BIDDER.TIMEOUT_ENDPOINT, - search: params - }); + const params = { aid: auctionId, ato: auctionTimeout }; + const timeoutRequestUrl = buildUrl({ + protocol: 'https', + hostname: BIDDER.HOST, + pathname: BIDDER.TIMEOUT_ENDPOINT, + search: params, + }); - triggerPixel(timeoutRequestUrl); - } catch (e) {} + fetch(timeoutRequestUrl, { + method: 'GET', + keepalive: true, + }).catch((e) => { + logError('Kargo: sendTimeoutData/fetch threw an error: ', e); + }); } function getImpression(bid) { @@ -522,7 +526,7 @@ function getImpression(bid) { } catch (e) { logError('Kargo: getFloor threw an error: ', e); } - imp.floor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; + imp.floor = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; } } diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js index 72df921e18f..fbb9974d52d 100644 --- a/modules/kimberliteBidAdapter.js +++ b/modules/kimberliteBidAdapter.js @@ -1,13 +1,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER } from '../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js' -import { deepSetValue } from '../src/utils.js'; +import { deepSetValue, replaceMacros } from '../src/utils.js'; +import {ORTB_MTYPES} from '../libraries/ortbConverter/processors/mediaType.js'; -const VERSION = '1.0.0'; +const VERSION = '1.1.0'; const BIDDER_CODE = 'kimberlite'; const METHOD = 'POST'; -const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; +export const ENDPOINT_URL = 'https://kimberlite.io/rtb/bid/pbjs'; const VERSION_INFO = { ver: '$prebid.version$', @@ -16,7 +17,6 @@ const VERSION_INFO = { const converter = ortbConverter({ context: { - mediaType: BANNER, netRevenue: true, ttl: 300 }, @@ -35,18 +35,38 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); imp.tagid = bidRequest.params.placementId; return imp; - } + }, + + bidResponse: function (buildBidResponse, bid, context) { + if (!bid.price) return; + + const [type] = Object.keys(context.bidRequest.mediaTypes); + if (Object.values(ORTB_MTYPES).includes(type)) { + context.mediaType = type; + } + + bid.adm = expandAuctionMacros(bid.adm, bid.price, context.ortbResponse.cur); + + if (bid.nurl && bid.nurl != '') { + bid.nurl = expandAuctionMacros(bid.nurl, bid.price, context.ortbResponse.cur); + } + + const bidResponse = buildBidResponse(bid, context); + return bidResponse; + }, }); export const spec = { code: BIDDER_CODE, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid: (bidRequest = {}) => { const { params, mediaTypes } = bidRequest; let isValid = Boolean(params && params.placementId); if (mediaTypes && mediaTypes[BANNER]) { isValid = isValid && Boolean(mediaTypes[BANNER].sizes); + } else if (mediaTypes && mediaTypes[VIDEO]) { + isValid = isValid && Boolean(mediaTypes[VIDEO].mimes); } else { isValid = false; } @@ -58,7 +78,10 @@ export const spec = { return { method: METHOD, url: ENDPOINT_URL, - data: converter.toORTB({ bidderRequest, bidRequests }) + data: converter.toORTB({ + bidRequests, + bidderRequest + }) } }, @@ -68,4 +91,12 @@ export const spec = { } }; +export function expandAuctionMacros(str, price, currency) { + if (!str) return; + + const defaultCurrency = 'RUB'; + + return replaceMacros(str, {AUCTION_PRICE: price, AUCTION_CURRENCY: currency || defaultCurrency}); +}; + registerBidder(spec); diff --git a/modules/kimberliteBidAdapter.md b/modules/kimberliteBidAdapter.md index c165f1073aa..06749f2c8e0 100644 --- a/modules/kimberliteBidAdapter.md +++ b/modules/kimberliteBidAdapter.md @@ -20,7 +20,7 @@ var adUnits = [ code: 'test-div', mediaTypes: { banner: { - sizes: [[320, 250], [640, 480]], + sizes: [[320, 250], [640, 480]], // Required. } }, bids: [ @@ -34,3 +34,32 @@ var adUnits = [ } ] ``` + +## Video AdUnit + +```javascript +var adUnits = [ + { + code: 'test-div', + mediaTypes: { + video: { + // ORTB 2.5 options. + mimes: ['video/mp4'], // Required. + // Other options are optional. + placement: 1, + protocols: [3, 6], + linearity: 1, + startdelay: 0 + } + }, + bids: [ + { + bidder: "kimberlite", + params: { + placementId: 'testVideo' + } + } + ] + } +] +``` diff --git a/modules/kiviadsBidAdapter.js b/modules/kiviadsBidAdapter.js index 13739d57cb2..161ddad470f 100644 --- a/modules/kiviadsBidAdapter.js +++ b/modules/kiviadsBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'kiviads'; const AD_URL = 'https://lb.kiviads.com/pbjs'; const SYNC_URL = 'https://sync.kiviads.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: bidderRequest.coppa === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - gpp: bidderRequest.gppConsent || undefined, - tmax: bidderRequest.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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/koblerBidAdapter.js b/modules/koblerBidAdapter.js index 596e5b2695f..54e70686fcc 100644 --- a/modules/koblerBidAdapter.js +++ b/modules/koblerBidAdapter.js @@ -90,8 +90,6 @@ export const onTimeout = function (timeoutDataArray) { timeoutDataArray.forEach(timeoutData => { const query = parseQueryStringParameters({ ad_unit_code: timeoutData.adUnitCode, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auction_id: timeoutData.auctionId, bid_id: timeoutData.bidId, timeout: timeoutData.timeout, page_url: pageUrl, @@ -103,13 +101,6 @@ export const onTimeout = function (timeoutDataArray) { }; function getPageUrlFromRequest(validBidRequest, bidderRequest) { - // pageUrl is considered only when testing to ensure that non-test requests always contain the correct URL - if (isTest(validBidRequest) && config.getConfig('pageUrl')) { - // TODO: it's not clear what the intent is here - but all adapters should always respect pageUrl. - // With prebid 7, using `refererInfo.page` will do that automatically. - return config.getConfig('pageUrl'); - } - return (bidderRequest.refererInfo && bidderRequest.refererInfo.page) ? bidderRequest.refererInfo.page : window.location.href; @@ -125,7 +116,26 @@ function getPageUrlFromRefererInfo() { function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { const imps = validBidRequests.map(buildOpenRtbImpObject); const timeout = bidderRequest.timeout; - const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest) + const pageUrl = getPageUrlFromRequest(validBidRequests[0], bidderRequest); + // Kobler, a contextual advertising provider, does not process any personal data itself, so it is not part of TCF/GVL. + // However, it supports using select third-party creatives in its platform, some of which require certain permissions + // in order to be shown. Kobler's bidder checks if necessary permissions are present to avoid bidding + // with ineligible creatives. + let purpose2Given; + let purpose3Given; + if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.vendorData) { + const vendorData = bidderRequest.gdprConsent.vendorData + const purposeData = vendorData.purpose; + const restrictions = vendorData.publisher ? vendorData.publisher.restrictions : null; + const restrictionForPurpose2 = restrictions ? (restrictions[2] ? Object.values(restrictions[2])[0] : null) : null; + purpose2Given = restrictionForPurpose2 === 1 ? ( + purposeData && purposeData.consents && purposeData.consents[2] + ) : ( + restrictionForPurpose2 === 0 + ? false : (purposeData && purposeData.legitimateInterests && purposeData.legitimateInterests[2]) + ); + purpose3Given = purposeData && purposeData.consents && purposeData.consents[3]; + } const request = { id: bidderRequest.bidderRequestId, at: 1, @@ -138,7 +148,13 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { site: { page: pageUrl, }, - test: getTestAsNumber(validBidRequests[0]) + test: getTestAsNumber(validBidRequests[0]), + ext: { + kobler: { + tcf_purpose_2_given: purpose2Given, + tcf_purpose_3_given: purpose3Given + } + } }; return JSON.stringify(request); @@ -147,7 +163,7 @@ function buildOpenRtbBidRequestPayload(validBidRequests, bidderRequest) { function buildOpenRtbImpObject(validBidRequest) { const sizes = getSizes(validBidRequest); const mainSize = sizes[0]; - const floorInfo = getFloorInfo(validBidRequest, mainSize); + const floorInfo = getFloorInfo(validBidRequest, mainSize) || {}; return { id: validBidRequest.bidId, diff --git a/modules/krushmediaBidAdapter.js b/modules/krushmediaBidAdapter.js index 876f0ebabc6..255e3670254 100644 --- a/modules/krushmediaBidAdapter.js +++ b/modules/krushmediaBidAdapter.js @@ -1,163 +1,40 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { + isBidRequestValid, + buildRequestsBase, + interpretResponse, + getUserSyncs, + buildPlacementProcessingFunction, +} from '../libraries/teqblazeUtils/bidderUtils.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; +const SYNC_URL = 'https://cs.krushmedia.com'; + +const addCustomFieldsToPlacement = (bid, bidderRequest, placement) => { + placement.key = bid.params.key; + placement.traffic = placement.adFormat; + if (placement.adFormat === VIDEO) { + placement.wPlayer = placement.playerSize?.[0]?.[0]; + placement.hPlayer = placement.playerSize?.[0]?.[1]; } -} +}; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } +const placementProcessingFunction = buildPlacementProcessingFunction({ addCustomFieldsToPlacement }); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + return buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest, placementProcessingFunction }); +}; 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) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page); - winTop = window.top; - } catch (e) { - location = winTop.location; - 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 - }]; - } + isBidRequestValid: isBidRequestValid(['key']), + buildRequests, + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/kubientBidAdapter.js b/modules/kubientBidAdapter.js index 57cbe6acd07..8ccfa4ab059 100644 --- a/modules/kubientBidAdapter.js +++ b/modules/kubientBidAdapter.js @@ -1,4 +1,4 @@ -import { isArray, deepAccess } from '../src/utils.js'; +import { isArray, deepAccess, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import { config } from '../src/config.js'; @@ -33,7 +33,7 @@ export const spec = { 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') { + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD') { let floor = parseFloat(floorInfo.floor) if (!isNaN(floor) && floor > 0) { adSlot.floor = parseFloat(floorInfo.floor); diff --git a/modules/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 5a5536e0c1a..f11d71f3318 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -155,7 +155,7 @@ function getFloorPrice(bid, mediaType) { currency: MAIN_CURRENCY, mediaType: mediaType, size: '*' - }); + }) || {}; floor = floorResult.currency === MAIN_CURRENCY && floorResult.floor ? floorResult.floor : 0; } @@ -417,6 +417,7 @@ function populateVideoParams(params, bid) { const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); const placement = deepAccess(bid, `mediaTypes.video.placement`); + const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); const playbackMethod = getPlaybackMethod(bid); const skip = deepAccess(bid, `mediaTypes.video.skip`); @@ -435,7 +436,9 @@ function populateVideoParams(params, bid) { if (placement) { params.placement = placement; } - + if (plcmt) { + params.plcmt = plcmt; + } if (playbackMethod) { params.playbackMethod = playbackMethod; } diff --git a/modules/kueezRtbBidAdapter.js b/modules/kueezRtbBidAdapter.js index ba33f3ade9a..c0fb17672af 100644 --- a/modules/kueezRtbBidAdapter.js +++ b/modules/kueezRtbBidAdapter.js @@ -1,338 +1,43 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} 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 {config} from '../src/config.js'; +import { + createBuildRequestsFn, + createInterpretResponseFn, + createUserSyncGetter, + isBidRequestValid +} from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1165; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'kueezrtb'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.kueezrtb.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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { +function createUniqueRequestData(hashUrl, bid) { const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, auctionId, transactionId, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - // TODO: fix auctionId/transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: auctionId, - transactionId: transactionId, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data + return { + auctionId, + transactionId, }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; } -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = bidderRequest.timeout ?? config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.kueezrtb.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.kueezrtb.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.kueezrtb.com/api/sync/iframe', + imageSyncUrl: 'https://sync.kueezrtb.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/lemmaDigitalBidAdapter.js b/modules/lemmaDigitalBidAdapter.js index dde7c25d9b9..f2d677f14db 100644 --- a/modules/lemmaDigitalBidAdapter.js +++ b/modules/lemmaDigitalBidAdapter.js @@ -295,7 +295,7 @@ export var spec = { [BANNER, VIDEO].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))) { + if (utils.isPlainObject(floorInfo) && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { let mediaTypeFloor = parseFloat(floorInfo.floor); bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)); } diff --git a/modules/limelightDigitalBidAdapter.js b/modules/limelightDigitalBidAdapter.js index 6c589f536c2..f69ae8f76eb 100644 --- a/modules/limelightDigitalBidAdapter.js +++ b/modules/limelightDigitalBidAdapter.js @@ -32,7 +32,7 @@ function isBidResponseValid(bid) { export const spec = { code: BIDDER_CODE, - aliases: ['pll', 'iionads', 'apester', 'adsyield'], + aliases: ['pll', 'iionads', 'apester', 'adsyield', 'tgm'], supportedMediaTypes: [BANNER, VIDEO], /** @@ -128,6 +128,8 @@ function buildRequest(winTop, host, adUnits, bidderRequest) { deviceWidth: winTop.screen.width, deviceHeight: winTop.screen.height, adUnits: adUnits, + ortb2: bidderRequest?.ortb2, + refererInfo: bidderRequest?.refererInfo, sua: bidderRequest?.ortb2?.device?.sua, page: bidderRequest?.ortb2?.site?.page || bidderRequest?.refererInfo?.page } @@ -164,6 +166,7 @@ function buildPlacement(bidRequest) { } }), type: bidRequest.params.adUnitType.toUpperCase(), + ortb2Imp: bidRequest.ortb2Imp, publisherId: bidRequest.params.publisherId, userIdAsEids: bidRequest.userIdAsEids, supplyChain: bidRequest.schain, diff --git a/modules/liveIntentIdSystem.js b/modules/liveIntentIdSystem.js index 786feeb8052..2ccbb911478 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -1,387 +1,14 @@ -/** - * This module adds LiveIntentId to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/liveIntentIdSystem - * @requires module:modules/userId - */ -import { triggerPixel, logError } from '../src/utils.js'; -import { ajaxBuilder } from '../src/ajax.js'; -import { submodule } from '../src/hook.js'; -import { LiveConnect } from 'live-connect-js'; // eslint-disable-line prebid/validate-imports -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; -import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; -import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; -import { getRefererInfo } from '../src/refererDetection.js'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse - */ - -const DEFAULT_AJAX_TIMEOUT = 5000 -const EVENTS_TOPIC = 'pre_lips' -const MODULE_NAME = 'liveIntentId'; -const LI_PROVIDER_DOMAIN = 'liveintent.com'; -export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -const defaultRequestedAttributes = {'nonId': true} -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; - -/** - * This function is used in tests - */ -export function reset() { - if (window && window.liQ_instances) { - window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) - window.liQ_instances = []; - } - liveIntentIdSubmodule.setModuleMode(null) - eventFired = false; - liveConnect = null; -} - -/** - * This function is also used in tests - */ -export function setEventFiredFlag() { - eventFired = true; -} - -function parseLiveIntentCollectorConfig(collectConfig) { - const config = {}; - 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); - config.ajaxTimeout = collectConfig.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; - return config; -} - -/** - * Create requestedAttributes array to pass to liveconnect - * @function - * @param {Object} overrides - object with boolean values that will override defaults { 'foo': true, 'bar': false } - * @returns {Array} - */ -function parseRequestedAttributes(overrides) { - function createParameterArray(config) { - return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [k] : []); - } - if (typeof overrides === 'object') { - return createParameterArray({...defaultRequestedAttributes, ...overrides}) +function loadModule() { + // Load appropriate module based on the build flag. Constant folding ensures + // that the other one will not be included in the bundle. + // eslint-disable-next-line no-constant-condition + if ('$$LIVE_INTENT_MODULE_MODE$$' === 'external') { + // eslint-disable-next-line no-restricted-globals + return require('../libraries/liveIntentId/externalIdSystem.js') } else { - return createParameterArray(defaultRequestedAttributes); - } -} - -function initializeLiveConnect(configParams) { - configParams = configParams || {}; - if (liveConnect) { - return liveConnect; - } - - const publisherId = configParams.publisherId || 'any'; - const identityResolutionConfig = { - publisherId: publisherId, - requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) - }; - if (configParams.url) { - identityResolutionConfig.url = configParams.url - } - - identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; - - const liveConnectConfig = parseLiveIntentCollectorConfig(configParams.liCollectConfig); - - if (!liveConnectConfig.appId && configParams.distributorId) { - liveConnectConfig.distributorId = configParams.distributorId; - identityResolutionConfig.source = configParams.distributorId; - } else { - identityResolutionConfig.source = configParams.partner || 'prebid' - } - - liveConnectConfig.wrapperName = 'prebid'; - liveConnectConfig.trackerVersion = '$prebid.version$'; - liveConnectConfig.identityResolutionConfig = identityResolutionConfig; - liveConnectConfig.identifiersToResolve = configParams.identifiersToResolve || []; - liveConnectConfig.fireEventDelay = configParams.fireEventDelay; - const usPrivacyString = uspDataHandler.getConsentData(); - if (usPrivacyString) { - liveConnectConfig.usPrivacyString = usPrivacyString; + // eslint-disable-next-line no-restricted-globals + return require('../libraries/liveIntentId/idSystem.js') } - const gdprConsent = gdprDataHandler.getConsentData() - if (gdprConsent) { - liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; - liveConnectConfig.gdprConsent = gdprConsent.consentString; - } - const gppConsent = gppDataHandler.getConsentData(); - if (gppConsent) { - liveConnectConfig.gppString = gppConsent.gppString; - liveConnectConfig.gppApplicableSections = gppConsent.applicableSections; - } - // The second param is the storage object, LS & Cookie manipulation uses PBJS - // The third param is the ajax and pixel object, the ajax and pixel use PBJS - liveConnect = liveIntentIdSubmodule.getInitializer()(liveConnectConfig, storage, calls); - if (configParams.emailHash) { - liveConnect.push({ hash: configParams.emailHash }) - } - return liveConnect; } -function tryFireEvent() { - if (!eventFired && liveConnect) { - const eventDelay = liveConnect.config.fireEventDelay || 500 - setTimeout(() => { - const instances = window.liQ_instances - instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) - if (!eventFired && liveConnect) { - liveConnect.fire(); - } - }, eventDelay) - } -} - -/** @type {Submodule} */ -export const liveIntentIdSubmodule = { - moduleMode: '$$LIVE_INTENT_MODULE_MODE$$', - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - - setModuleMode(mode) { - this.moduleMode = mode - }, - getInitializer() { - return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode) - }, - - /** - * 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 {SubmoduleConfig|undefined} config - * @returns {{lipb:Object}} - */ - decode(value, config) { - const configParams = (config && config.params) || {}; - function composeIdObject(value) { - const result = {}; - - // old versions stored lipbid in unifiedId. Ensure that we can still read the data. - const lipbid = value.nonId || value.unifiedId - if (lipbid) { - value.lipbid = lipbid - delete value.unifiedId - result.lipb = value - } - - // Lift usage of uid2 by exposing uid2 if we were asked to resolve it. - // As adapters are applied in lexicographical order, we will always - // be overwritten by the 'proper' uid2 module if it is present. - if (value.uid2) { - result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.bidswitch) { - result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.medianet) { - result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.magnite) { - result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.index) { - result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.openx) { - result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.pubmatic) { - result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.sovrn) { - result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } - } - - if (value.thetradedesk) { - result.lipb = {...result.lipb, tdid: value.thetradedesk} - result.tdid = { 'id': value.thetradedesk, ext: { rtiPartner: 'TDID', provider: getRefererInfo().domain || LI_PROVIDER_DOMAIN } } - delete result.lipb.thetradedesk - } - - return result - } - - if (!liveConnect) { - initializeLiveConnect(configParams); - } - tryFireEvent(); - - return composeIdObject(value); - }, - - /** - * 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) || {}; - const liveConnect = initializeLiveConnect(configParams); - if (!liveConnect) { - return; - } - tryFireEvent(); - const result = function(callback) { - liveConnect.resolve( - response => { - callback(response); - }, - error => { - logError(`${MODULE_NAME}: ID fetch encountered an error: `, error); - callback(); - } - ) - } - - return { callback: result }; - }, - eids: { - ...UID1_EIDS, - ...UID2_EIDS, - 'lipb': { - getValue: function(data) { - return data.lipbid; - }, - source: 'liveintent.com', - atype: 3, - getEidExt: function(data) { - if (Array.isArray(data.segments) && data.segments.length) { - return { - segments: data.segments - }; - } - } - }, - 'bidswitch': { - source: 'bidswitch.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'medianet': { - source: 'media.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'magnite': { - source: 'rubiconproject.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'index': { - source: 'liveintent.indexexchange.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'openx': { - source: 'openx.net', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'pubmatic': { - source: 'pubmatic.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - }, - 'sovrn': { - source: 'liveintent.sovrn.com', - atype: 3, - getValue: function(data) { - return data.id; - }, - getUidExt: function(data) { - if (data.ext) { - return data.ext; - } - } - } - } -}; - -submodule('userId', liveIntentIdSubmodule); +export const liveIntentIdSubmodule = loadModule() diff --git a/modules/livewrappedAnalyticsAdapter.js b/modules/livewrappedAnalyticsAdapter.js index 2797664e954..413147b4fa4 100644 --- a/modules/livewrappedAnalyticsAdapter.js +++ b/modules/livewrappedAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import { timestamp, logInfo, getWindowTop } from '../src/utils.js'; +import { timestamp, logInfo } from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import { EVENTS, STATUS } from '../src/constants.js'; @@ -171,7 +171,7 @@ livewrappedAnalyticsAdapter.sendEvents = function() { timeouts: getTimeouts(sentRequests.gdpr, sentRequests.auctionIds), bidAdUnits: getbidAdUnits(), rf: getAdRenderFailed(sentRequests.auctionIds), - rcv: getAdblockerRecovered() + ext: initOptions.ext }; if (events.requests.length == 0 && @@ -185,12 +185,6 @@ livewrappedAnalyticsAdapter.sendEvents = function() { ajax(initOptions.endpoint || URL, undefined, JSON.stringify(events), {method: 'POST'}); }; -function getAdblockerRecovered() { - try { - return getWindowTop().I12C && getWindowTop().I12C.Morph === 1; - } catch (e) {} -} - function getSentRequests() { var sentRequests = []; var gdpr = []; diff --git a/modules/lkqdBidAdapter.js b/modules/lkqdBidAdapter.js index d4b1cdea0d1..6c97f64e6a8 100644 --- a/modules/lkqdBidAdapter.js +++ b/modules/lkqdBidAdapter.js @@ -47,7 +47,7 @@ export const spec = { const GDPR = BIDDER_GDPR || bid.params.gdpr || null; const GDPRS = BIDDER_GDPRS || bid.params.gdprs || null; const DNT = bid.params.dnt || null; - const BID_FLOOR = bid.params.flrd > bid.params.flrmp ? bid.params.flrd : bid.params.flrmp; + const BID_FLOOR = 0; const VIDEO_BID = bid.video ? bid.video : {}; const requestData = { @@ -157,7 +157,6 @@ export const spec = { h: sizes[1], skip: VIDEO_BID.skip || 0, playbackmethod: VIDEO_BID.playbackmethod || [1], - placement: (bid.params.execution === 'outstream' || VIDEO_BID.context === 'outstream') ? 5 : 1, ext: { lkqdcustomparameters: {} }, diff --git a/modules/lm_kiviadsBidAdapter.js b/modules/lm_kiviadsBidAdapter.js index 7c3085047c4..7295eb33258 100644 --- a/modules/lm_kiviadsBidAdapter.js +++ b/modules/lm_kiviadsBidAdapter.js @@ -1,213 +1,16 @@ -import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, getBidIdParameter, logError, isArray} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - */ - -const CUR = 'USD'; const BIDDER_CODE = 'lm_kiviads'; const ENDPOINT = 'https://pbjs.kiviads.live'; -/** - * 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. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('pid', req.params)) { - logError('Env or pid is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const {refererInfo = {}, gdprConsent = {}, uspConsent} = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - request.auctionId = req.ortb2?.source?.tid; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - pid: req.params.pid - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT + '/bid', - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - 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. - */ -function interpretResponse(serverResponse, {bidderRequest}) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * 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. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [type, url] = pixel; - const sync = {type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}`}; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, aliases: ['kivi'], supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/loganBidAdapter.js b/modules/loganBidAdapter.js index 7aa82e3046c..4e2652e452f 100644 --- a/modules/loganBidAdapter.js +++ b/modules/loganBidAdapter.js @@ -1,54 +1,18 @@ -import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; +import { getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { buildUserSyncs, interpretResponse, isBidRequestValid, getBidFloor, consentCheck } from '../libraries/precisoUtils/bidUtilsCommon.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 (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} +const SYNC_URL = 'https://ssp-cookie.logan.ai'; export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, + isBidRequestValid: isBidRequestValid, buildRequests: (validBidRequests = [], bidderRequest) => { // convert Native ORTB definition to old-style prebid native definition @@ -67,14 +31,7 @@ export const spec = { placements: placements }; - if (bidderRequest) { - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - if (bidderRequest.gdprConsent) { - request.gdpr = bidderRequest.gdprConsent; - } - } + consentCheck(bidderRequest, request); const len = validBidRequests.length; for (let i = 0; i < len; i++) { @@ -99,6 +56,7 @@ export const spec = { placement.protocols = mediaType[VIDEO].protocols; placement.startdelay = mediaType[VIDEO].startdelay; placement.placement = mediaType[VIDEO].placement; + placement.plcmt = mediaType[VIDEO].plcmt; placement.skip = mediaType[VIDEO].skip; placement.skipafter = mediaType[VIDEO].skipafter; placement.minbitrate = mediaType[VIDEO].minbitrate; @@ -122,42 +80,11 @@ export const spec = { }; }, - 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; - }, - + interpretResponse: interpretResponse, 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 - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); } + }; registerBidder(spec); diff --git a/modules/logicadBidAdapter.js b/modules/logicadBidAdapter.js index fe4dd83c9e2..e7c5300d072 100644 --- a/modules/logicadBidAdapter.js +++ b/modules/logicadBidAdapter.js @@ -46,7 +46,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } @@ -74,7 +74,7 @@ function newBidRequest(bidRequest, bidderRequest) { mediaTypes: bidRequest.mediaTypes, } - const fledgeEnabled = deepAccess(bidderRequest, 'fledgeEnabled') + const fledgeEnabled = deepAccess(bidderRequest, 'paapi.enabled') if (fledgeEnabled) { const ae = deepAccess(bidRequest, 'ortb2Imp.ext.ae'); if (ae) { diff --git a/modules/lotamePanoramaIdSystem.js b/modules/lotamePanoramaIdSystem.js index 64d631c2469..3be7b261723 100644 --- a/modules/lotamePanoramaIdSystem.js +++ b/modules/lotamePanoramaIdSystem.js @@ -11,13 +11,11 @@ import { isBoolean, buildUrl, isEmpty, - isArray, - isEmptyStr + isArray } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { submodule } from '../src/hook.js'; import {getStorageManager} from '../src/storageManager.js'; -import { uspDataHandler } from '../src/adapterManager.js'; import {MODULE_TYPE_UID} from '../src/activities/modules.js'; /** @@ -38,16 +36,24 @@ const MISSING_CORE_CONSENT = 111; const GVLID = 95; const ID_HOST = 'id.crwdcntrl.net'; const ID_HOST_COOKIELESS = 'c.ltmsphrcl.net'; +const DO_NOT_HONOR_CONFIG = false; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); let cookieDomain; +let appliedConfig = { + name: 'lotamePanoramaId', + storage: { + type: 'cookie&html5', + name: 'panoramaId' + } +}; /** * Set the Lotame First Party Profile ID in the first party namespace * @param {String} profileId */ function setProfileId(profileId) { - if (storage.cookiesAreEnabled()) { + if (cookiesAreEnabled()) { let expirationDate = new Date(timestamp() + NINE_MONTHS_MS).toUTCString(); storage.setCookie( KEY_PROFILE, @@ -58,7 +64,7 @@ function setProfileId(profileId) { undefined ); } - if (storage.hasLocalStorage()) { + if (localStorageIsEnabled()) { storage.setDataInLocalStorage(KEY_PROFILE, profileId, undefined); } } @@ -68,10 +74,10 @@ function setProfileId(profileId) { */ function getProfileId() { let profileId; - if (storage.cookiesAreEnabled()) { + if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) { profileId = storage.getCookie(KEY_PROFILE, undefined); } - if (!profileId && storage.hasLocalStorage()) { + if (!profileId && localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) { profileId = storage.getDataFromLocalStorage(KEY_PROFILE, undefined); } return profileId; @@ -83,21 +89,11 @@ function getProfileId() { */ function getFromStorage(key) { let value = null; - if (storage.cookiesAreEnabled()) { + if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) { value = storage.getCookie(key, undefined); } - if (storage.hasLocalStorage() && value === null) { - const storedValueExp = storage.getDataFromLocalStorage( - `${key}_exp`, undefined - ); - - if (storedValueExp === '' || storedValueExp === null) { - value = storage.getDataFromLocalStorage(key, undefined); - } else if (storedValueExp) { - if ((new Date(parseInt(storedValueExp, 10))).getTime() - Date.now() > 0) { - value = storage.getDataFromLocalStorage(key, undefined); - } - } + if (value === null && localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) { + value = storage.getDataFromLocalStorage(key, undefined); } return value; } @@ -115,7 +111,7 @@ function saveLotameCache( ) { if (key && value) { let expirationDate = new Date(expirationTimestamp).toUTCString(); - if (storage.cookiesAreEnabled()) { + if (cookiesAreEnabled()) { storage.setCookie( key, value, @@ -125,12 +121,7 @@ function saveLotameCache( undefined ); } - if (storage.hasLocalStorage()) { - storage.setDataInLocalStorage( - `${key}_exp`, - String(expirationTimestamp), - undefined - ); + if (localStorageIsEnabled()) { storage.setDataInLocalStorage(key, value, undefined); } } @@ -172,7 +163,7 @@ function getLotameLocalCache(clientId = undefined) { */ function clearLotameCache(key) { if (key) { - if (storage.cookiesAreEnabled()) { + if (cookiesAreEnabled(DO_NOT_HONOR_CONFIG)) { let expirationDate = new Date(0).toUTCString(); storage.setCookie( key, @@ -183,11 +174,50 @@ function clearLotameCache(key) { undefined ); } - if (storage.hasLocalStorage()) { + if (localStorageIsEnabled(DO_NOT_HONOR_CONFIG)) { storage.removeDataFromLocalStorage(key, undefined); } } } +/** + * @param {boolean} honorConfig - false to override for reading or deleting old cookies + * @returns {boolean} for whether we can write the cookie + */ +function cookiesAreEnabled(honorConfig = true) { + if (honorConfig) { + return storage.cookiesAreEnabled() && appliedConfig.storage.type.includes('cookie'); + } + return storage.cookiesAreEnabled(); +} +/** + * @param {boolean} honorConfig - false to override for reading or deleting old stored items + * @returns {boolean} for whether we can write the cookie + */ +function localStorageIsEnabled(honorConfig = true) { + if (honorConfig) { + return storage.hasLocalStorage() && appliedConfig.storage.type.includes('html5'); + } + return storage.hasLocalStorage(); +} +/** + * @param {SubmoduleConfig} config + * @returns {null|string} - string error if it finds one, null otherwise. + */ +function checkConfigHasErrorsAndReport(config) { + let error = null; + if (typeof config.storage !== 'undefined') { + Object.assign(appliedConfig.storage, appliedConfig.storage, config.storage); + const READABLE_MODULE_NAME = 'Lotame ID module'; + const PERMITTED_STORAGE_TYPES = ['cookie', 'html5', 'cookie&html5']; + if (typeof config.storage.name !== 'undefined' && config.storage.name !== KEY_ID) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is expected to be "${KEY_ID}", actual is "${config.storage.name}"`); + error = true; + } else if (config.storage.type !== 'undefined' && !PERMITTED_STORAGE_TYPES.includes(config.storage.type)) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is expected to be one of "${PERMITTED_STORAGE_TYPES.join(', ')}", actual is "${config.storage.type}"`); + } + } + return error; +} /** @type {Submodule} */ export const lotamePanoramaIdSubmodule = { /** @@ -222,6 +252,9 @@ export const lotamePanoramaIdSubmodule = { * @returns {IdResponse|undefined} */ getId(config, consentData, cacheIdObj) { + if (checkConfigHasErrorsAndReport(config)) { + return; + } cookieDomain = lotamePanoramaIdSubmodule.findRootDomain(); const configParams = (config && config.params) || {}; const clientId = configParams.clientId; @@ -249,18 +282,6 @@ export const lotamePanoramaIdSubmodule = { const storedUserId = getProfileId(); - // Add CCPA Consent data handling - const usp = uspDataHandler.getConsentData(); - - let usPrivacy; - if (typeof usp !== 'undefined' && !isEmpty(usp) && !isEmptyStr(usp)) { - usPrivacy = usp; - } - if (!usPrivacy) { - // fallback to 1st party cookie - usPrivacy = getFromStorage('us_privacy'); - } - const getRequestHost = function() { if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { return ID_HOST_COOKIELESS; @@ -281,22 +302,10 @@ export const lotamePanoramaIdSubmodule = { } 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; } - // Add usPrivacy to the url - if (usPrivacy) { - queryParams.us_privacy = usPrivacy; - } - // Add clientId to the url if (hasCustomClientId) { queryParams.c = clientId; diff --git a/modules/lotamePanoramaIdSystem.md b/modules/lotamePanoramaIdSystem.md index e960f4b5695..1fbc3f561c7 100644 --- a/modules/lotamePanoramaIdSystem.md +++ b/modules/lotamePanoramaIdSystem.md @@ -17,9 +17,32 @@ Retrieve the Lotame Panorama Id pbjs.setConfig({ usersync: { userIds: [ - { - name: 'lotamePanoramaId' // The only parameter that is needed - }], + { + name: 'lotamePanoramaId', + storage: { + name: 'panoramaId', + type: 'cookie&html5', + expires: 7 + } + } + ], } }); -``` \ No newline at end of file +``` + +| Parameters under `userSync.userIds[]` | Scope | Type | Description | Example | +| ---| --- | --- | --- | --- | +| name | Required | String | Name for the Lotame ID submodule | `"lotamePanoramaId"` | +| storage | Optional | Object | Configures how to cache User IDs locally in the browser | See [storage settings](#storage-settings) | + + +### Storage Settings + +The following settings are available for the `storage` property in the `userSync.userIds[]` object. Please note that inclusion of the `storage` property is optional, but if provided, all three attributes listed below *must* be specified: + +| Param name | Scope | Type | Description | Example | +| --- | --- | --- | --- | --- | +| name | Required | String| Name of the cookie or localStorage where the user ID will be stored; *must* be `"panoramaId"` | `"panoramaId"` | +| type | Required | String | `"cookie&html5"` (preferred) or `"cookie"` or `"html5"` | `"cookie&html5"` | +| expires | Required | Number | How long (in days) the user ID information will be stored. Lotame recommends `7`. | `7` | + diff --git a/modules/loyalBidAdapter.js b/modules/loyalBidAdapter.js index 30fdeb44233..e34ec89cf35 100644 --- a/modules/loyalBidAdapter.js +++ b/modules/loyalBidAdapter.js @@ -1,190 +1,17 @@ -import { logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'loyal'; const AD_URL = 'https://us-east-1.loyal.app/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, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor, - eids: [] - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.gdprConsent?.consentString) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - 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; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/luceadBidAdapter.js b/modules/luceadBidAdapter.js old mode 100644 new mode 100755 index ab7f96c4e60..ffc2307bcb8 --- a/modules/luceadBidAdapter.js +++ b/modules/luceadBidAdapter.js @@ -1,40 +1,42 @@ +/** + * @module modules/luceadBidAdapter + */ + import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {loadExternalScript} from '../src/adloader.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {getUniqueIdentifierStr, logInfo, deepSetValue} from '../src/utils.js'; +import {getUniqueIdentifierStr, deepSetValue, logInfo} from '../src/utils.js'; import {fetch} from '../src/ajax.js'; +const gvlid = 1309; const bidderCode = 'lucead'; -const bidderName = 'Lucead'; -let baseUrl = 'https://lucead.com'; -let staticUrl = 'https://s.lucead.com'; -let companionUrl = 'https://cdn.jsdelivr.net/gh/lucead/prebid-js-external-js-lucead@master/dist/prod.min.js'; -let endpointUrl = 'https://prebid.lucead.com/go'; const defaultCurrency = 'EUR'; const defaultTtl = 500; const aliases = ['adliveplus']; +const defaultRegion = 'eu'; +const domain = 'lucead.com' +let baseUrl = `https://${domain}`; +let staticUrl = `https://s.${domain}`; +let endpointUrl = baseUrl; function isDevEnv() { - return location.hash.includes('prebid-dev') || location.href.startsWith('https://ayads.io/test'); + return location.hash.includes('prebid-dev'); } function isBidRequestValid(bidRequest) { return !!bidRequest?.params?.placementId; } -export function log(msg, obj) { - logInfo(`${bidderName} - ${msg}`, obj); -} - function buildRequests(bidRequests, bidderRequest) { + const region = bidRequests[0]?.params?.region || defaultRegion; + endpointUrl = `https://${region}.${domain}`; + if (isDevEnv()) { baseUrl = location.origin; staticUrl = baseUrl; - companionUrl = `${staticUrl}/dist/prebid-companion.js`; - endpointUrl = `${baseUrl}/go`; + endpointUrl = `${baseUrl}`; } - log('buildRequests', { + logInfo('buildRequests', { bidRequests, bidderRequest, }); @@ -50,107 +52,134 @@ function buildRequests(bidRequests, bidderRequest) { getUniqueIdentifierStr, ortbConverter, deepSetValue, + is_sra: true, + region, }; - loadExternalScript(companionUrl, bidderCode, () => window.ayads_prebid && window.ayads_prebid(companionData)); + window.lucead_prebid_data = companionData; + const fn = window.lucead_prebid; + + if (fn && typeof fn === 'function') { + fn(companionData); + } - return bidRequests.map(bidRequest => ({ + return { method: 'POST', - url: `${endpointUrl}/prebid/sub`, + url: `${endpointUrl}/go/prebid/sra`, data: JSON.stringify({ request_id: bidderRequest.bidderRequestId, domain: location.hostname, - bid_id: bidRequest.bidId, - sizes: bidRequest.sizes, - media_types: bidRequest.mediaTypes, - fledge_enabled: bidderRequest.fledgeEnabled, - enable_contextual: bidRequest?.params?.enableContextual !== false, - enable_pa: bidRequest?.params?.enablePA !== false, - params: bidRequest.params, + bid_requests: bidRequests.map(bidRequest => { + return { + bid_id: bidRequest.bidId, + sizes: bidRequest.sizes, + media_types: bidRequest.mediaTypes, + placement_id: bidRequest.params.placementId, + schain: bidRequest.schain, + }; + }), }), options: { contentType: 'text/plain', withCredentials: false }, - })); + }; } function interpretResponse(serverResponse, bidRequest) { // @see required fields https://docs.prebid.org/dev-docs/bidder-adaptor.html - const response = serverResponse.body; - const bidRequestData = JSON.parse(bidRequest.data); - - const bids = response.enable_contextual !== false ? [{ - requestId: response?.bid_id || '1', // bid request id, the bid id - cpm: response?.cpm || 0, - width: (response?.size && response?.size?.width) || 300, - height: (response?.size && response?.size?.height) || 250, - currency: response?.currency || defaultCurrency, - ttl: response?.ttl || defaultTtl, - creativeId: response.ssp ? `ssp:${response.ssp}` : (response?.ad_id || '0'), - netRevenue: response?.netRevenue || true, - ad: response?.ad || '', + const response = serverResponse?.body; + const bidRequestData = JSON.parse(bidRequest?.data); + + const bids = (response?.bids || []).map(bid => ({ + requestId: bid?.bid_id || '1', // bid request id, the bid id + cpm: bid?.cpm || 0, + width: (bid?.size && bid?.size?.width) || 300, + height: (bid?.size && bid?.size?.height) || 250, + currency: bid?.currency || defaultCurrency, + ttl: bid?.ttl || defaultTtl, + creativeId: bid?.ssp ? `ssp:${bid.ssp}` : `${bid?.ad_id || 0}:${bid?.ig_id || 0}`, + netRevenue: bid?.net_revenue || true, + ad: bid?.ad || '', meta: { - advertiserDomains: response?.advertiserDomains || [], + advertiserDomains: bid?.advertiser_domains || [], }, - }] : null; - - log('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); - - if (response.enable_pa === false) { return bids; } - - const fledgeAuctionConfig = { - seller: baseUrl, - decisionLogicUrl: `${baseUrl}/js/ssp.js`, - interestGroupBuyers: [baseUrl], - perBuyerSignals: {}, - auctionSignals: { - size: bidRequestData.sizes ? {width: bidRequestData?.sizes[0][0] || 300, height: bidRequestData?.sizes[0][1] || 250} : null, - }, - }; + })); - const fledgeAuctionConfigs = [{bidId: response.bid_id, config: fledgeAuctionConfig}]; + logInfo('interpretResponse', {serverResponse, bidRequest, bidRequestData, bids}); + + if (response?.enable_pa === false) { return bids; } + + const fledgeAuctionConfigs = (response.bids || []).map(bid => ({ + bidId: bid?.bid_id, + config: { + seller: baseUrl, + decisionLogicUrl: `${baseUrl}/js/ssp.js`, + interestGroupBuyers: [baseUrl], + requestedSize: bid?.size, + auctionSignals: { + size: bid?.size, + }, + perBuyerSignals: { + [baseUrl]: { + prebid_paapi: true, + prebid_bid_id: bid?.bid_id, + prebid_request_id: bidRequestData.request_id, + placement_id: bid.placement_id, + // floor, + is_sra: true, + endpoint_url: endpointUrl, + }, + } + } + })); - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } -function report(type = 'impression', data = {}) { +function report(type, data) { // noinspection JSCheckFunctionSignatures - return fetch(`${endpointUrl}/report/${type}`, { - body: JSON.stringify(data), + return fetch(`${endpointUrl}/go/report/${type}`, { + body: JSON.stringify({ + ...data, + domain: location.hostname, + }), method: 'POST', - contentType: 'text/plain' + contentType: 'text/plain', }); } function onBidWon(bid) { - log('Bid won', bid); + logInfo('Bid won', bid); let data = { bid_id: bid?.bidId, - placement_id: bid?.params ? bid?.params[0]?.placementId : 0, + placement_id: bid.params ? (bid?.params[0]?.placementId || '0') : '0', spent: bid?.cpm, currency: bid?.currency, }; - if (bid.creativeId) { - if (bid.creativeId.toString().startsWith('ssp:')) { - data.ssp = bid.creativeId.split(':')[1]; + if (bid?.creativeId) { + const parts = bid.creativeId.toString().split(':'); + + if (parts[0] === 'ssp') { + data.ssp = parts[1]; } else { - data.ad_id = bid.creativeId; + data.ad_id = parts[0] + data.ig_id = parts[1] } } - return report(`impression`, data); + return report('impression', data); } function onTimeout(timeoutData) { - log('Timeout from adapter', timeoutData); + logInfo('Timeout from adapter', timeoutData); } export const spec = { code: bidderCode, - // gvlid: BIDDER_GVLID, + gvlid, aliases, isBidRequestValid, buildRequests, diff --git a/modules/luceadBidAdapter.md b/modules/luceadBidAdapter.md old mode 100644 new mode 100755 index 953c911cd2b..41e3730897a --- a/modules/luceadBidAdapter.md +++ b/modules/luceadBidAdapter.md @@ -1,29 +1,41 @@ -# Overview +# Lucead Bid Adapter -Module Name: Lucead Bidder Adapter +- Module Name: Lucead Bidder Adapter +- Module Type: Bidder Adapter +- Maintainer: prebid@lucead.com -Module Type: Bidder Adapter +## Description -Maintainer: prebid@lucead.com +Module that connects to Lucead demand source. -# Description +## Adapter configuration -Module that connects to Lucead demand source to fetch bids. +## Ad units parameters -# Test Parameters +### Type definition + +```typescript +type Params = { + placementId: string; + region?: 'eu' | 'us' | 'ap'; +}; ``` -const adUnits = [ - { - code: 'test-div', - sizes: [[300, 250]], - bids: [ - { - bidder: 'lucead', - params: { - placementId: '2', - } - } - ] - } - ]; + +### Example code +```javascript +const adUnits=[ + { + code:'test-div', + sizes:[[300,250]], + bids:[ + { + bidder: 'lucead', + params:{ + placementId: '1', + region: 'us', // optional: 'eu', 'us', 'ap' + } + } + ] + } +]; ``` diff --git a/modules/lunamediahbBidAdapter.js b/modules/lunamediahbBidAdapter.js index 66838014e18..6ad42a4f3ca 100644 --- a/modules/lunamediahbBidAdapter.js +++ b/modules/lunamediahbBidAdapter.js @@ -1,140 +1,19 @@ -import { logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'lunamediahb'; const AD_URL = 'https://balancer.lmgssp.com/?c=o&m=multi'; const SYNC_URL = 'https://cookie.lmgssp.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !bid.ttl || !bid.currency) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl) || Boolean(bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(parseInt(bid.params.placementId))); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let winTop = window; - let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - 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 = { - placementId: bid.params.placementId, - bidId: bid.bidId, - schain: bid.schain || {}, - }; - const mediaType = bid.mediaTypes - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.traffic = BANNER; - } else if (mediaType && mediaType[VIDEO]) { - if (mediaType[VIDEO].playerSize) { - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; - } - placement.traffic = VIDEO; - placement.videoContext = mediaType[VIDEO].context || 'instream' - } else if (mediaType && mediaType[NATIVE]) { - placement.native = mediaType[NATIVE]; - placement.traffic = NATIVE; - } - placements.push(placement); - } - - return { - method: 'POST', - url: AD_URL, - data: request - }; - }, - - interpretResponse: (serverResponse) => { - let response = []; - for (let i = 0; i < serverResponse.body.length; i++) { - let resItem = serverResponse.body[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/luponmediaBidAdapter.js b/modules/luponmediaBidAdapter.js index 20fa601bade..63435437967 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -6,15 +6,17 @@ import { isArray, isEmpty, isFn, + isPlainObject, logError, logMessage, logWarn, - parseSizesInput + parseSizesInput, + sizeTupleToRtbSize, + sizesToSizeTuples } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER} from '../src/mediaTypes.js'; -import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'luponmedia'; const ENDPOINT_URL = 'https://rtb.adxpremium.services/openrtb2/auction'; @@ -220,19 +222,6 @@ export const spec = { hasSynced = true; return allUserSyncs; - }, - onBidWon: bid => { - const bidString = JSON.stringify(bid); - spec.sendWinningsToServer(bidString); - }, - sendWinningsToServer: data => { - let mutation = `mutation {createWin(input: {win: {eventData: "${window.btoa(data)}"}}) {win {createTime } } }`; - let dataToSend = JSON.stringify({ query: mutation }); - - ajax('https://analytics.adxpremium.services/graphql', null, dataToSend, { - contentType: 'application/json', - method: 'POST' - }); } }; @@ -285,16 +274,8 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { let bannerSizes = []; if (bannerParams && bannerParams.sizes) { - const sizes = 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 }; - }); - + const format = sizesToSizeTuples(bannerParams.sizes).map(sizeTupleToRtbSize); bannerSizes = format; } @@ -340,7 +321,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { } catch (e) { logError('LuponMedia: getFloor threw an error: ', e); } - bidFloor = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; + bidFloor = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? parseFloat(floorInfo.floor) : undefined; } else { bidFloor = parseFloat(deepAccess(bidRequest, 'params.floor')); } @@ -469,9 +450,7 @@ function newOrtbBidRequest(bidRequest, bidderRequest, currentImps) { deepSetValue(data, 'ext.prebid.bidderconfig.0', bidderData); } - // TODO: bidRequest.fpd is not the right place for pbadslot - who's filling that in, if anyone? - // is this meant to be bidRequest.ortb2Imp.ext.data.pbadslot? - const pbAdSlot = deepAccess(bidRequest, 'fpd.context.pbAdSlot'); + const pbAdSlot = deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); if (typeof pbAdSlot === 'string' && pbAdSlot) { deepSetValue(data.imp[0].ext, 'context.data.adslot', pbAdSlot); } diff --git a/modules/mabidderBidAdapter.js b/modules/mabidderBidAdapter.js index 8d97f48f604..6df68bda269 100644 --- a/modules/mabidderBidAdapter.js +++ b/modules/mabidderBidAdapter.js @@ -1,6 +1,5 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { getGlobal } from '../src/prebidGlobal.js'; import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'mabidder'; @@ -39,7 +38,7 @@ export const spec = { url: baseUrl, method: 'POST', data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', bids: bids, url: bidderRequest.refererInfo.page || '', referer: bidderRequest.refererInfo.ref || '', diff --git a/modules/madvertiseBidAdapter.js b/modules/madvertiseBidAdapter.js index 3b031623aef..9fc7ceb68aa 100644 --- a/modules/madvertiseBidAdapter.js +++ b/modules/madvertiseBidAdapter.js @@ -1,5 +1,4 @@ import { parseSizesInput, _each } from '../src/utils.js'; -import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; /** @@ -27,9 +26,6 @@ export const spec = { if (sizes.length > 0 && sizes[0] === undefined) { return false; } - if (typeof bid.params.floor == 'undefined' || parseFloat(bid.params.floor) < 0.01) { - bid.params.floor = 0.01; - } return typeof bid.params.s != 'undefined'; }, @@ -58,7 +54,7 @@ export const spec = { } if (bidderRequest && bidderRequest.gdprConsent) { - src = src + '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0') + '&consent[0][format]=' + config.getConfig('consentManagement.cmpApi') + '&consent[0][value]=' + bidderRequest.gdprConsent.consentString; + src = src + '&gdpr=' + (bidderRequest.gdprConsent.gdprApplies ? '1' : '0') + '&consent[0][format]=iab&consent[0][value]=' + bidderRequest.gdprConsent.consentString; } return { diff --git a/modules/magniteAnalyticsAdapter.js b/modules/magniteAnalyticsAdapter.js index 225607ad6d3..7eb20439f5f 100644 --- a/modules/magniteAnalyticsAdapter.js +++ b/modules/magniteAnalyticsAdapter.js @@ -47,13 +47,13 @@ const pbsErrorMap = { 4: 'request-error', 999: 'generic-error' } -let cookieless; let browser; let pageReferer; let auctionIndex = 0; // count of auctions on page let accountId; let endpoint; +let cookieless; let prebidGlobal = getGlobal(); const { @@ -65,7 +65,7 @@ const { BID_TIMEOUT, BID_WON, BILLABLE_EVENT, - SEAT_NON_BID, + PBS_ANALYTICS, BID_REJECTED } = EVENTS; @@ -334,9 +334,9 @@ const getTopLevelDetails = () => { // Add DM wrapper details if (rubiConf.wrapperName) { - let rule; + let rule = rubiConf.rule_name; if (cookieless) { - rule = rubiConf.rule_name ? rubiConf.rule_name.concat('_cookieless') : 'cookieless'; + rule = rule ? rule.concat('_cookieless') : 'cookieless'; } payload.wrapper = { name: rubiConf.wrapperName, @@ -703,6 +703,7 @@ magniteAdapter.disableAnalytics = function () { magniteAdapter._oldEnable = enableMgniAnalytics; endpoint = undefined; accountId = undefined; + cookieless = undefined; auctionIndex = 0; resetConfs(); getHook('callPrebidCache').getHooks({ hook: callPrebidCacheHook }).remove(); @@ -926,8 +927,8 @@ magniteAdapter.track = ({ eventType, args }) => { const bidStatus = args.rejectionReason === REJECTION_REASON.FLOOR_NOT_MET ? BID_REJECTED_IPF : 'rejected'; handleBidResponse(args, bidStatus); break; - case SEAT_NON_BID: - handleNonBidEvent(args); + case PBS_ANALYTICS: + handlePbsAnalytics(args); break; case BIDDER_DONE: const serverError = deepAccess(args, 'serverErrors.0'); @@ -1018,8 +1019,27 @@ magniteAdapter.track = ({ eventType, args }) => { } }; -const handleNonBidEvent = function(args) { - const {seatnonbid, auctionId} = args; +const handlePbsAnalytics = function (args) { + const {seatnonbid, auctionId, atag} = args; + if (seatnonbid) { + handleNonBidEvent(seatnonbid, auctionId); + } + if (atag) { + handleAtagEvent(atag, auctionId); + } +} + +const handleAtagEvent = function (atag, auctionId) { + const tags = findTimeoutOptimization(atag) + tags.forEach(tag => { + tag.activities.forEach(activity => { + if (activity.name === 'optimize-tmax' && activity.status === 'success') { + setAnalyticsTagData(activity.results[0]?.values, deepAccess(cache, `auctions.${auctionId}.auction`)) + } + }) + }); +} +const handleNonBidEvent = function(seatnonbid, auctionId) { const auction = deepAccess(cache, `auctions.${auctionId}.auction`); // if no auction just bail if (!auction) { @@ -1049,6 +1069,28 @@ const handleNonBidEvent = function(args) { }); }; +const findTimeoutOptimization = (atag) => { + let timeoutOpt; + atag.forEach(tag => { + if (tag.module === 'mgni-timeout-optimization') { + timeoutOpt = tag.analyticstags; + } + }) + return timeoutOpt; +} +const setAnalyticsTagData = (values, auction) => { + let data = { + name: values.scenario, + rule: values.rule, + value: values.tmax + } + + const experiments = deepAccess(auction, 'experiments') || []; + experiments.push(data); + + deepSetValue(auction, 'experiments', experiments); +} + const statusMap = { 0: { status: 'no-bid' diff --git a/modules/marsmediaAnalyticsAdapter.js b/modules/marsmediaAnalyticsAdapter.js deleted file mode 100644 index f1e53a3c20c..00000000000 --- a/modules/marsmediaAnalyticsAdapter.js +++ /dev/null @@ -1,53 +0,0 @@ -import {ajax} from '../src/ajax.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adapterManager from '../src/adapterManager.js'; -import {getGlobal} from '../src/prebidGlobal.js'; - -/**** - * Mars Media Analytics - * Contact: prebid@m-m-g.com‏ - * Developer: Chen Saadia - */ - -const MARS_BIDDER_CODE = 'marsmedia'; -const analyticsType = 'endpoint'; -const MARS_VERSION = '1.0.1'; -const MARS_ANALYTICS_URL = 'https://prebid_stats.mars.media/prebidjs/api/analytics.php'; -var events = {}; - -var marsmediaAnalyticsAdapter = Object.assign(adapter( - { - MARS_ANALYTICS_URL, - analyticsType - }), -{ - track({eventType, args}) { - if (typeof args !== 'undefined' && args.bidderCode === MARS_BIDDER_CODE) { - events[eventType] = args; - } - - if (eventType === 'auctionEnd') { - setTimeout(function() { - ajax( - MARS_ANALYTICS_URL, - { - success: function() {}, - error: function() {} - }, - JSON.stringify({act: 'prebid_analytics', params: events, 'pbjs': getGlobal().getBidResponses(), ver: MARS_VERSION}), - { - method: 'POST' - } - ); - }, 3000); - } - } -} -); - -adapterManager.registerAnalyticsAdapter({ - adapter: marsmediaAnalyticsAdapter, - code: 'marsmedia' -}); - -export default marsmediaAnalyticsAdapter; diff --git a/modules/marsmediaBidAdapter.js b/modules/marsmediaBidAdapter.js index 82a25af60d1..1d322a23a77 100644 --- a/modules/marsmediaBidAdapter.js +++ b/modules/marsmediaBidAdapter.js @@ -1,6 +1,6 @@ 'use strict'; -import { deepAccess, getDNT, parseSizesInput, isArray, getWindowTop, deepSetValue, triggerPixel, getWindowSelf } from '../src/utils.js'; +import { deepAccess, getDNT, parseSizesInput, isArray, getWindowTop, deepSetValue, triggerPixel, getWindowSelf, isPlainObject } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -341,7 +341,7 @@ function MarsmediaAdapter() { size: '*' }); - if (typeof floorInfo === 'object' && + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = floorInfo.floor; diff --git a/modules/mathildeadsBidAdapter.js b/modules/mathildeadsBidAdapter.js index 929cee8f3c0..0ecfe63765b 100644 --- a/modules/mathildeadsBidAdapter.js +++ b/modules/mathildeadsBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'mathildeads'; const AD_URL = 'https://endpoint2.mathilde-ads.com/pbjs'; const SYNC_URL = 'https://cs2.mathilde-ads.com'; -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 } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - placementId, - bidId, - schain, - bidfloor - }; - - if (mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } - - if (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; - } - - if (mediaTypes[NATIVE]) { - placement.adFormat = NATIVE; - placement.native = mediaTypes[NATIVE]; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - 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[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } - - if (mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } - - if (mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } - - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest?.refererInfo?.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/mediaConsortiumBidAdapter.js b/modules/mediaConsortiumBidAdapter.js new file mode 100644 index 00000000000..a1cd6586735 --- /dev/null +++ b/modules/mediaConsortiumBidAdapter.js @@ -0,0 +1,273 @@ +import {BANNER, VIDEO} from '../src/mediaTypes.js' +import {registerBidder} from '../src/adapters/bidderFactory.js' +import {generateUUID, isPlainObject, isArray, logWarn, deepClone} from '../src/utils.js' +import {Renderer} from '../src/Renderer.js' +import {OUTSTREAM} from '../src/video.js' +import {config} from '../src/config.js'; +import { getStorageManager } from '../src/storageManager.js'; + +const BIDDER_CODE = 'mediaConsortium' + +const PROFILE_API_USAGE_CONFIG_KEY = 'useProfileApi' +const ONE_PLUS_X_ID_USAGE_CONFIG_KEY = 'readOnePlusXId' + +const SYNC_ENDPOINT = 'https://relay.hubvisor.io/v1/sync/big' +const AUCTION_ENDPOINT = 'https://relay.hubvisor.io/v1/auction/big' + +const XANDR_OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstreamVideo.js'; + +export const OPTIMIZATIONS_STORAGE_KEY = 'media_consortium_optimizations' + +const SYNC_TYPES = { + image: 'image', + redirect: 'image', + iframe: 'iframe' +} + +const storageManager = getStorageManager({ bidderCode: BIDDER_CODE }); + +export const spec = { + version: '0.0.1', + code: BIDDER_CODE, + gvlid: 1112, + supportedMediaTypes: [BANNER, VIDEO], + isBidRequestValid(bid) { + return true + }, + buildRequests(bidRequests, bidderRequest) { + const useProfileApi = config.getConfig(PROFILE_API_USAGE_CONFIG_KEY) ?? false + const readOnePlusXId = config.getConfig(ONE_PLUS_X_ID_USAGE_CONFIG_KEY) ?? false + + const { + auctionId, + bids, + gdprConsent: {gdprApplies = false, consentString} = {}, + ortb2: {device, site} + } = bidderRequest + + const currentTimestamp = Date.now() + const optimizations = getOptimizationsFromLocalStorage() + + const impressions = bids.reduce((acc, bidRequest) => { + const {bidId, adUnitCode, mediaTypes} = bidRequest + const optimization = optimizations[adUnitCode] + + if (optimization) { + const {expiresAt, isEnabled} = optimization + + if (expiresAt >= currentTimestamp && !isEnabled) { + return acc + } + } + + let finalizedMediatypes = deepClone(mediaTypes) + + if (mediaTypes.video && mediaTypes.video.context !== OUTSTREAM) { + logWarn(`Filtering video request for adUnitCode ${adUnitCode} because context is not ${OUTSTREAM}`) + + if (Object.keys(finalizedMediatypes).length > 1) { + delete finalizedMediatypes.video + } else { + return acc + } + } + + return acc.concat({id: bidId, adUnitCode, mediaTypes: finalizedMediatypes}) + }, []) + + if (!impressions.length) { + return + } + + const request = { + id: auctionId ?? generateUUID(), + impressions, + device, + site, + user: { + ids: {} + }, + regulations: { + gdpr: { + applies: gdprApplies, + consentString + } + }, + timeout: 3600, + options: { + useProfileApi + } + } + + if (readOnePlusXId) { + const fpId = getFpIdFromLocalStorage() + + if (fpId) { + request.user.ids['1plusX'] = fpId + } + } + + const syncData = { + gdpr: gdprApplies, + ad_unit_codes: impressions.map(({adUnitCode}) => adUnitCode).join(',') + } + + if (consentString) { + syncData.gdpr_consent = consentString + } + + return [ + { + method: 'GET', + url: SYNC_ENDPOINT, + data: syncData + }, + { + method: 'POST', + url: AUCTION_ENDPOINT, + data: request + } + ] + }, + interpretResponse(serverResponse, params) { + if (!isValidResponse(serverResponse)) return [] + + const {body: {bids, optimizations}} = serverResponse + + if (optimizations && isArray(optimizations)) { + const currentTimestamp = Date.now() + + const optimizationsToStore = optimizations.reduce((acc, optimization) => { + const {adUnitCode, isEnabled, ttl} = optimization + + return { + ...acc, + [adUnitCode]: {isEnabled, expiresAt: currentTimestamp + ttl} + } + }, getOptimizationsFromLocalStorage()) + + storageManager.setDataInLocalStorage(OPTIMIZATIONS_STORAGE_KEY, JSON.stringify(optimizationsToStore)) + } + + return bids.map((bid) => { + const { + impressionId, + price: {cpm, currency}, + dealId, + ad: { + creative: {id, mediaType, size: {width, height}, markup} + }, + ttl = 360 + } = bid + + const formattedBid = { + requestId: impressionId, + cpm, + currency, + dealId, + ttl, + netRevenue: true, + creativeId: id, + mediaType, + width, + height, + ad: markup, + adUrl: null + } + + if (mediaType === VIDEO) { + const impressionRequest = params.data.impressions.find(({id}) => id === impressionId) + + formattedBid.vastXml = markup + + if (impressionRequest) { + formattedBid.renderer = buildXandrOutstreamRenderer(impressionId, impressionRequest.adUnitCode) + } else { + logWarn(`Could not find adUnitCode matching the impressionId ${impressionId} to setup the renderer`) + } + } + + return formattedBid + }) + }, + getUserSyncs(syncOptions, serverResponses) { + if (serverResponses.length !== 2) { + return + } + + const [sync] = serverResponses + + return sync.body?.bidders?.reduce((acc, {type, url}) => { + const syncType = SYNC_TYPES[type] + + if (!syncType || !url) { + return acc + } + + return acc.concat({type: syncType, url}) + }, []) + } +} + +registerBidder(spec) + +export function getOptimizationsFromLocalStorage() { + try { + const storedOptimizations = storageManager.getDataFromLocalStorage(OPTIMIZATIONS_STORAGE_KEY) + + return storedOptimizations ? JSON.parse(storedOptimizations) : {} + } catch (err) { + return {} + } +} + +function getFpIdFromLocalStorage() { + try { + return storageManager.getDataFromLocalStorage('ope_fpid') + } catch (err) { + return null + } +} + +function isValidResponse(response) { + return isPlainObject(response) && + isPlainObject(response.body) && + isArray(response.body.bids) +} + +function buildXandrOutstreamRenderer(bidId, adUnitCode) { + const renderer = Renderer.install({ + id: bidId, + url: XANDR_OUTSTREAM_RENDERER_URL, + loaded: false, + adUnitCode, + targetId: adUnitCode + }); + + try { + renderer.setRender(xandrOutstreamRenderer); + } catch (err) { + logWarn('Prebid Error calling setRender on renderer', err); + } + + return renderer; +} + +function xandrOutstreamRenderer(bid) { + const {width, height, adUnitCode, vastXml} = bid + + bid.renderer.push(() => { + window.ANOutstreamVideo.renderAd({ + sizes: [width, height], + targetId: adUnitCode, + rendererOptions: { + showBigPlayButton: false, + showProgressBar: 'bar', + content: vastXml, + showVolume: false, + allowFullscreen: true, + skippable: false + } + }); + }); +} diff --git a/modules/mediaConsortiumBidAdapter.md b/modules/mediaConsortiumBidAdapter.md new file mode 100644 index 00000000000..b78627077cb --- /dev/null +++ b/modules/mediaConsortiumBidAdapter.md @@ -0,0 +1,79 @@ +# Media Consortium Bid adapter + +## Overview + +``` +- Module Name: Media Consortium Bidder Adapter +- Module Type: Media Consortium Bidder Adapter +- Maintainer: mediaconsortium-develop@bi.garage.co.jp +``` + +## Description + +Module that connects to Media Consortium demand sources and supports the following media types: `banner`, `video`. + +To get access to the full feature set of the adapter you'll need to allow localstorage usage in the `bidderSettings`. + +```javascript + pbjs.bidderSettings = { + mediaConsortium: { + storageAllowed: true + } + } +``` + +## Managing 1plusX profile API usage and FPID retrieval + +You can use the `setBidderConfig` function to enable or disable 1plusX profile API usage and fpid retrieval. + +If the keys found below are not defined, their values will default to `false`. + +```javascript + pbjs.setBidderConfig({ + bidders: ['mediaConsortium'], + config: { + // Controls the 1plusX profile API usage + useProfileApi: true, + // Controls the 1plusX fpid retrieval + readOnePlusXId: true + } + }); +``` + +## Test Parameters + +```javascript + var adUnits = [ + { + code: 'div-prebid-banner', + mediaTypes:{ + banner: { + sizes: [[300, 250]], + } + }, + bids:[ + { + bidder: 'mediaConsortium', + params: {} + } + ] + }, + { + code: 'div-prebid-video', + mediaTypes:{ + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + }, + bids:[ + { + bidder: 'mediaConsortium', + params: {} + } + ] + } + ]; +``` diff --git a/modules/mediabramaBidAdapter.js b/modules/mediabramaBidAdapter.js index caf6854fe03..9722f1672ba 100644 --- a/modules/mediabramaBidAdapter.js +++ b/modules/mediabramaBidAdapter.js @@ -1,155 +1,22 @@ -import { - isFn, - isStr, - deepAccess, - getWindowTop, - triggerPixel -} from '../src/utils.js'; + import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { bidWinReport, buildBidRequests, buildUserSyncs, interpretResponse, isBidRequestValid } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER_CODE = 'mediabrama'; const AD_URL = 'https://prebid.mediabrama.com/pbjs'; const SYNC_URL = 'https://prebid.mediabrama.com/sync'; -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); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - const winTop = 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] : '', - 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) - }; - - if (typeof bid.userId !== 'undefined') { - placement.userId = bid.userId; - } - - const mediaType = bid.mediaTypes; - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.adFormat = BANNER; - } - - 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; - }, - + isBidRequestValid: isBidRequestValid, + buildRequests: buildBidRequests(AD_URL), + interpretResponse: interpretResponse, 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 - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); }, - - onBidWon: (bid) => { - const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); - triggerPixel(bid.nurl); - } - } + onBidWon: bidWinReport }; registerBidder(spec); diff --git a/modules/mediafilterRtdProvider.js b/modules/mediafilterRtdProvider.js index fae5c9e769b..5472c4a6ce0 100644 --- a/modules/mediafilterRtdProvider.js +++ b/modules/mediafilterRtdProvider.js @@ -15,6 +15,7 @@ import { logError, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; /** The event type for Media Filter. */ export const MEDIAFILTER_EVENT_TYPE = 'com.mediatrust.pbjs.'; @@ -54,7 +55,8 @@ export const MediaFilter = { * @param {string} configurationHash - The configuration hash. */ setupScript: function(configurationHash) { - loadExternalScript(MEDIAFILTER_BASE_URL.concat(configurationHash), 'mediafilter', () => {}); + loadExternalScript(MEDIAFILTER_BASE_URL.concat(configurationHash), MODULE_TYPE_RTD, 'mediafilter', () => { + }); }, /** diff --git a/modules/mediafuseBidAdapter.js b/modules/mediafuseBidAdapter.js index 5e31f60d3b5..b70d2bf30a5 100644 --- a/modules/mediafuseBidAdapter.js +++ b/modules/mediafuseBidAdapter.js @@ -24,16 +24,14 @@ import {find, includes} from '../src/polyfill.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {bidderSettings} from '../src/bidderSettings.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {APPNEXUS_CATEGORY_MAPPING} from '../libraries/categoryTranslationMapping/index.js'; import { getANKewyordParamFromMaps, - getANKeywordParam, - transformBidderParamKeywords + getANKeywordParam } from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore, fill} from '../libraries/appnexusUtils/anUtils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; import {chunk} from '../libraries/chunk/chunk.js'; /** @@ -351,31 +349,6 @@ export const spec = { } }, - transformBidParams: function (params, isOpenRtb) { - params = convertTypes({ - 'member': 'string', - 'invCode': 'string', - 'placementId': 'number', - 'keywords': transformBidderParamKeywords, - 'publisherId': 'number' - }, params); - - if (isOpenRtb) { - params.use_pmt_rule = (typeof params.usePaymentRule === 'boolean') ? params.usePaymentRule : false; - if (params.usePaymentRule) { delete params.usePaymentRule; } - - Object.keys(params).forEach(paramKey => { - let convertedKey = convertCamelToUnderscore(paramKey); - if (convertedKey !== paramKey) { - params[convertedKey] = params[paramKey]; - delete params[paramKey]; - } - }); - } - - return params; - }, - /** * Add element selector to javascript tracker to improve native viewability * @param {Bid} bid @@ -756,7 +729,7 @@ function bidToTag(bid) { if (!isEmpty(bid.params.keywords)) { tag.keywords = getANKewyordParamFromMaps(bid.params.keywords); } - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } @@ -1063,7 +1036,7 @@ function hideSASIframe(elementId) { function outstreamRender(bid) { hidedfpContainer(bid.adUnitCode); hideSASIframe(bid.adUnitCode); - // push to render queue because ANOutstreamVideo may not be loaded yet + // push to render queue because ANOutstreamVideo may not be loaded bid.renderer.push(() => { window.ANOutstreamVideo.renderAd({ tagId: bid.adResponse.tag_id, diff --git a/modules/mediagoBidAdapter.js b/modules/mediagoBidAdapter.js index 8f687d30ff3..1811f62e6ba 100644 --- a/modules/mediagoBidAdapter.js +++ b/modules/mediagoBidAdapter.js @@ -5,6 +5,12 @@ import * as utils from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink, getReferrer } from '../libraries/fpdUtils/pageInfo.js'; +import { getDevice } from '../libraries/fpdUtils/deviceInfo.js'; +import { getBidFloor } from '../libraries/currencyUtils/floor.js'; +import { transformSizes, normalAdSize } from '../libraries/sizeUtils/tranformSize.js'; +import { getHLen } from '../libraries/navigatorData/navigatorData.js'; + // import { config } from '../src/config.js'; // import { isPubcidEnabled } from './pubCommonId.js'; @@ -34,64 +40,6 @@ export const COOKIE_KEY_MGUID = '__mguid_'; const COOKIE_KEY_PMGUID = '__pmguid_'; const COOKIE_RETENTION_TIME = 365 * 24 * 60 * 60 * 1000; // 1 year let reqTimes = 0; -/** - * get page title - * @returns {string} - */ - -export function getPageTitle(win = window) { - try { - const ogTitle = win.top.document.querySelector('meta[property="og:title"]') - return win.top.document.title || (ogTitle && ogTitle.content) || ''; - } catch (e) { - const ogTitle = document.querySelector('meta[property="og:title"]') - return document.title || (ogTitle && ogTitle.content) || ''; - } -} - -/** - * get page description - * - * @returns {string} - */ -export function getPageDescription(win = window) { - let element; - - try { - element = win.top.document.querySelector('meta[name="description"]') || - win.top.document.querySelector('meta[property="og:description"]') - } catch (e) { - element = document.querySelector('meta[name="description"]') || - document.querySelector('meta[property="og:description"]') - } - - return (element && element.content) || ''; -} - -/** - * get page keywords - * @returns {string} - */ -export function getPageKeywords(win = window) { - let element; - - try { - element = win.top.document.querySelector('meta[name="keywords"]'); - } catch (e) { - element = document.querySelector('meta[name="keywords"]'); - } - - return (element && element.content) || ''; -} - -/** - * get connection downlink - * @returns {number} - */ -export function getConnectionDownLink(win = window) { - const nav = win.navigator || {}; - return nav && nav.connection && nav.connection.downlink >= 0 ? nav.connection.downlink.toString() : undefined; -} /** * get pmg uid @@ -135,54 +83,6 @@ function getProperty(obj, ...keys) { return o; } -/** - * 是不是移动设备或者平板 - * @return {boolean} - */ -function isMobileAndTablet() { - let check = false; - (function (a) { - let reg1 = new RegExp( - [ - '(android|bbd+|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|android|ipad|playbook|silk' - ].join(''), - 'i' - ); - let reg2 = new RegExp( - [ - '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-' - ].join(''), - 'i' - ); - if (reg1.test(a) || reg2.test(a.substr(0, 4))) { - check = true; - } - })(navigator.userAgent || navigator.vendor || window.opera); - return check; -} - /** * 获取底价 * @param {*} bid @@ -205,62 +105,9 @@ function isMobileAndTablet() { // } // return floor; // } -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; - } -} - -/** - * 将尺寸转为RTB识别的尺寸 - * - * @param {Array|Object} requestSizes 配置尺寸 - * @return {Object} - */ -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; -} // 支持的广告尺寸 -const mediagoAdSize = [ - { w: 300, h: 250 }, - { w: 300, h: 600 }, - { w: 728, h: 90 }, - { w: 970, h: 250 }, - { w: 320, h: 50 }, - { w: 160, h: 600 }, - { w: 320, h: 180 }, - { w: 320, h: 100 }, - { w: 336, h: 280 } -]; +const mediagoAdSize = normalAdSize; /** * 获取广告位配置 @@ -340,21 +187,6 @@ function getItems(validBidRequests, bidderRequest) { return items; } -/** - * @param {BidRequest} bidRequest - * @param bidderRequest - * @returns {string} - */ -function getReferrer(bidRequest = {}, bidderRequest = {}) { - let pageUrl; - if (bidRequest.params && bidRequest.params.referrer) { - pageUrl = bidRequest.params.referrer; - } else { - pageUrl = utils.deepAccess(bidderRequest, 'refererInfo.page'); - } - return pageUrl; -} - /** * get current time to UTC string * @returns utc string @@ -386,7 +218,7 @@ function getParam(validBidRequests, bidderRequest) { const cat = utils.deepAccess(bidderRequest, 'ortb2.site.cat'); reqTimes += 1; - let isMobile = isMobileAndTablet() ? 1 : 0; + let isMobile = getDevice() ? 1 : 0; // input test status by Publisher. more frequently for test true req let isTest = validBidRequests[0].params.test || 0; let auctionId = getProperty(bidderRequest, 'auctionId'); @@ -399,7 +231,6 @@ function getParam(validBidRequests, bidderRequest) { const timeout = bidderRequest.timeout || 2000; const firstPartyData = bidderRequest.ortb2; - const topWindow = window.top; const title = getPageTitle(); const desc = getPageDescription(); const keywords = getPageKeywords(); @@ -436,12 +267,10 @@ function getParam(validBidRequests, bidderRequest) { title: title ? title.slice(0, 100) : undefined, desc: desc ? desc.slice(0, 300) : undefined, keywords: keywords ? keywords.slice(0, 100) : undefined, - hLen: topWindow.history?.length || undefined, + hLen: getHLen(), }, device: { nbw: getConnectionDownLink(), - hc: topWindow.navigator?.hardwareConcurrency || undefined, - dm: topWindow.navigator?.deviceMemory || undefined, } }, user: { diff --git a/modules/mediaimpactBidAdapter.js b/modules/mediaimpactBidAdapter.js index 4ce11201507..a1f04a44142 100644 --- a/modules/mediaimpactBidAdapter.js +++ b/modules/mediaimpactBidAdapter.js @@ -1,6 +1,5 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import { buildUrl } from '../src/utils.js' -import {ajax} from '../src/ajax.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { buildBidRequestsAndParams, postRequest, buildEndpointUrl } from '../libraries/mediaImpactUtils/index.js'; const BIDDER_CODE = 'mediaimpact'; export const ENDPOINT_PROTOCOL = 'https'; @@ -15,79 +14,25 @@ export const spec = { }, buildRequests: function (validBidRequests, bidderRequest) { - // TODO does it make sense to fall back to window.location.href? const referer = bidderRequest?.refererInfo?.page || window.location.href; - let bidRequests = []; - let beaconParams = { - tag: [], - partner: [], - sizes: [], - referer: '' - }; - - validBidRequests.forEach(function(validBidRequest) { - let sizes = validBidRequest.sizes; - if (typeof validBidRequest.params.sizes !== 'undefined') { - sizes = validBidRequest.params.sizes; - } - - let bidRequestObject = { - adUnitCode: validBidRequest.adUnitCode, - sizes: sizes, - bidId: validBidRequest.bidId, - referer: referer - }; - - if (parseInt(validBidRequest.params.unitId)) { - bidRequestObject.unitId = parseInt(validBidRequest.params.unitId); - beaconParams.tag.push(validBidRequest.params.unitId); - } - - if (parseInt(validBidRequest.params.partnerId)) { - bidRequestObject.unitId = 0; - bidRequestObject.partnerId = parseInt(validBidRequest.params.partnerId); - beaconParams.partner.push(validBidRequest.params.partnerId); - } - - bidRequests.push(bidRequestObject); - - beaconParams.sizes.push(spec.joinSizesToString(sizes)); - beaconParams.referer = encodeURIComponent(referer); - }); - - if (beaconParams.partner.length > 0) { - beaconParams.partner = beaconParams.partner.join(','); - } else { - delete beaconParams.partner; - } - - beaconParams.tag = beaconParams.tag.join(','); - beaconParams.sizes = beaconParams.sizes.join(','); + // Use the common function to build bidRequests and beaconParams + const { bidRequests, beaconParams } = buildBidRequestsAndParams(validBidRequests, referer); - let adRequestUrl = buildUrl({ - protocol: ENDPOINT_PROTOCOL, - hostname: ENDPOINT_DOMAIN, - pathname: ENDPOINT_PATH, - search: beaconParams - }); + const adRequestUrl = buildEndpointUrl( + ENDPOINT_PROTOCOL, + ENDPOINT_DOMAIN, + ENDPOINT_PATH, + beaconParams + ); return { method: 'POST', url: adRequestUrl, - data: JSON.stringify(bidRequests) + data: JSON.stringify(bidRequests), }; }, - joinSizesToString: function(sizes) { - let res = []; - sizes.forEach(function(size) { - res.push(size.join('x')); - }); - - return res.join('|'); - }, - interpretResponse: function (serverResponse, bidRequest) { const validBids = JSON.parse(bidRequest.data); @@ -96,16 +41,13 @@ export const spec = { } return validBids - .map(bid => ({ - bid: bid, - ad: serverResponse.body[bid.adUnitCode] - })) + .map(bid => ({ bid: bid, ad: serverResponse.body[bid.adUnitCode] })) .filter(item => item.ad) .map(item => spec.adResponse(item.bid, item.ad)); }, - adResponse: function(bid, ad) { - const bidObject = { + adResponse: function (bid, ad) { + return { requestId: bid.bidId, ad: ad.ad, cpm: ad.cpm, @@ -115,37 +57,27 @@ export const spec = { creativeId: ad.creativeId, netRevenue: ad.netRevenue, currency: ad.currency, - winNotification: ad.winNotification - } - - bidObject.meta = {}; - if (ad.adomain && ad.adomain.length > 0) { - bidObject.meta.advertiserDomains = ad.adomain; - } - - return bidObject; + winNotification: ad.winNotification, + meta: ad.meta || {}, + }; }, - onBidWon: function(data) { - data.winNotification.forEach(function(unitWon) { - let adBidWonUrl = buildUrl({ - protocol: ENDPOINT_PROTOCOL, - hostname: ENDPOINT_DOMAIN, - pathname: unitWon.path - }); + onBidWon: function (data) { + data.winNotification.forEach(function (unitWon) { + const adBidWonUrl = buildEndpointUrl( + ENDPOINT_PROTOCOL, + ENDPOINT_DOMAIN, + unitWon.path + ); if (unitWon.method === 'POST') { - spec.postRequest(adBidWonUrl, JSON.stringify(unitWon.data)); + postRequest(adBidWonUrl, JSON.stringify(unitWon.data)); } }); return true; }, - postRequest(endpoint, data) { - ajax(endpoint, null, data, {method: 'POST'}); - }, - getUserSyncs: function(syncOptions, serverResponses, gdprConsent, uspConsent) { const syncs = []; @@ -210,7 +142,6 @@ export const spec = { return syncs; }, - -} +}; registerBidder(spec); diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index f4967fed170..efe9eb90256 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -11,6 +11,7 @@ import { isFn, isInteger, isNumber, + isPlainObject, isStr, logError, logWarn, @@ -66,6 +67,7 @@ const ORTB_VIDEO_PARAMS = { h: value => isInteger(value), startdelay: value => isInteger(value), placement: value => [1, 2, 3, 4, 5].indexOf(value) !== -1, + plcmt: value => [1, 2, 3, 4].indexOf(value) !== -1, linearity: value => [1, 2].indexOf(value) !== -1, skip: value => [0, 1].indexOf(value) !== -1, skipmin: value => isInteger(value), @@ -138,7 +140,7 @@ function getFloor(bid, mediaType, size = '*') { size }) - return (!isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) ? floor.floor : false + return (isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY) ? floor.floor : false } /** diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 68927cc6b13..1b4ccc45c88 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -8,7 +8,8 @@ import { logError, logInfo, triggerPixel, - uniques + uniques, + deepSetValue } from '../src/utils.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; @@ -18,6 +19,7 @@ import {getRefererInfo} from '../src/refererDetection.js'; import {AUCTION_COMPLETED, AUCTION_IN_PROGRESS, getPriceGranularity} from '../src/auction.js'; import {includes} from '../src/polyfill.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {convertCurrency} from '../libraries/currencyUtils/currency.js'; const analyticsType = 'endpoint'; const ENDPOINT = 'https://pb-logs.media.net/log?logid=kfk&evtid=prebid_analytics_events_client'; @@ -36,8 +38,7 @@ const PRICE_GRANULARITY = { }; const MEDIANET_BIDDER_CODE = 'medianet'; -// eslint-disable-next-line no-undef -const PREBID_VERSION = getGlobal().version; +const PREBID_VERSION = '$prebid.version$' const ERROR_CONFIG_JSON_PARSE = 'analytics_config_parse_fail'; const ERROR_CONFIG_FETCH = 'analytics_config_ajax_fail'; const ERROR_WINNING_BID_ABSENT = 'winning_bid_absent'; @@ -113,6 +114,7 @@ class Configure { pbv: PREBID_VERSION, pbav: ANALYTICS_VERSION, flt: 1, + enableDbf: 1 } } @@ -304,21 +306,17 @@ class BidWrapper { this.bidObjs.push(bidObj); } - getAdSlotBids(adSlot) { - const bidResponses = this.getAdSlotBidObjs(adSlot); - return bidResponses.map((bid) => bid.getLoggingData()); + getAdSlotBidRequests(adSlot) { + return this.bidReqs.filter((bid) => bid.adUnitCode === adSlot); } - getAdSlotBidObjs(adSlot) { - const bidResponses = this.bidObjs - .filter((bid) => bid.adUnitCode === adSlot); - const remResponses = this.bidReqs.filter(bid => !bid.used && bid.adUnitCode === adSlot); - return [...bidResponses, ...remResponses]; + getAdSlotBidResponses(adSlot) { + return this.bidObjs.filter((bid) => bid.adUnitCode === adSlot); } } class Bid { - constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes) { + constructor(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, resSizes) { this.bidId = bidId; this.bidder = bidder; this.src = src; @@ -349,6 +347,14 @@ class Bid { this.used = false; this.originalRequestId = bidId; this.requestId = undefined; + this.advUrl = undefined; + this.latestAcid = undefined; + this.originalCurrency = undefined; + this.currMul = undefined; + this.inCurrMul = undefined; + this.res_mtype = undefined; + this.res_sizes = resSizes; + this.req_mtype = mediaType; } get size() { @@ -375,7 +381,7 @@ class Bid { cbdp: this.dfpbd, dfpbd: this.dfpbd, szs: this.allMediaTypeSizes.join('|'), - size: this.size, + size: (this.res_sizes || [this.size]).join('|'), mtype: this.mediaType, dId: this.dealId, winner: this.winner, @@ -390,6 +396,13 @@ class Bid { flrrule: this.floorRule, ext: JSON.stringify(this.ext), rtime: this.serverLatencyMillis, + advurl: this.advUrl, + lacid: this.latestAcid, + icurr: this.originalCurrency, + imul: this.inCurrMul, + omul: this.currMul, + res_mtype: this.res_mtype, + req_mtype: this.req_mtype } } } @@ -455,11 +468,12 @@ class Auction { return this.bidWrapper.findBidObj(key, value) } - getAdSlotBids(adSlot) { - return this.bidWrapper.getAdSlotBids(adSlot); + getAdSlotBidRequests(adSlot) { + return this.bidWrapper.getAdSlotBidRequests(adSlot); } - getAdSlotBidObjs(adSlot) { - return this.bidWrapper.getAdSlotBidObjs(adSlot); + + getAdSlotBidResponses(adSlot) { + return this.bidWrapper.getAdSlotBidResponses(adSlot); } _mergeFieldsToLog(objParams) { @@ -559,8 +573,8 @@ function _getSizes(mediaTypes, sizes) { } function bidResponseHandler(bid) { - const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder } = bid; - const {originalCpm, creativeId, adId, currency} = bid; + const { width, height, mediaType, cpm, requestId, timeToRespond, auctionId, dealId, originalRequestId, bidder, meta } = bid; + let {originalCpm, creativeId, adId, currency, originalCurrency} = bid; if (!(auctions[auctionId] instanceof Auction)) { return; @@ -576,13 +590,26 @@ function bidResponseHandler(bid) { bidObj = {}; isBidOverridden = false; } + currency = currency ? currency.toUpperCase() : ''; + originalCurrency = originalCurrency ? originalCurrency.toUpperCase() : currency; Object.assign(bidObj, bidReq, { cpm, width, height, mediaType, timeToRespond, dealId, creativeId, originalRequestId, requestId }, - { adId, currency } + { adId, currency, originalCurrency } ); bidObj.floorPrice = deepAccess(bid, 'floorData.floorValue'); bidObj.floorRule = deepAccess(bid, 'floorData.floorRule'); bidObj.originalCpm = originalCpm || cpm; + bidObj.advUrl = meta && meta.advertiserDomains && meta.advertiserDomains.join(','); + bidObj.currMul = 1; + bidObj.inCurrMul = 1; + if (bidObj.originalCurrency !== 'USD') { + bidObj.originalCpm = exchangeCurrency(bidObj.originalCpm, bidObj.originalCurrency, 'USD'); + bidObj.inCurrMul = exchangeCurrency(1, 'USD', bidObj.originalCurrency) + } + if (bidObj.currency !== 'USD') { + bidObj.cpm = exchangeCurrency(bidObj.cpm, bidObj.currency, 'USD'); + bidObj.currMul = exchangeCurrency(1, 'USD', bidObj.currency) + } let dfpbd = deepAccess(bid, 'adserverTargeting.hb_pb'); if (!dfpbd) { let priceGranularity = getPriceGranularity(bid); @@ -607,9 +634,19 @@ function bidResponseHandler(bid) { if (typeof bid.serverResponseTimeMs !== 'undefined') { bidObj.serverLatencyMillis = bid.serverResponseTimeMs; } + bidObj.res_mtype = mediaType; !isBidOverridden && auctions[auctionId].addBidObj(bidObj); } +function exchangeCurrency(price, fromCurrency, toCurrency) { + try { + return convertCurrency(price, fromCurrency, toCurrency, false).toFixed(4) + } catch (e) { + logError(`Media.net Analytics Adapter: Could not convert ${fromCurrency} to ${toCurrency} for price ${price}`); + } + return price; +} + function noBidResponseHandler({ auctionId, bidId }) { if (!(auctions[auctionId] instanceof Auction)) { return; @@ -631,7 +668,7 @@ function bidTimeoutHandler(timedOutBids) { if (!(auctions[auctionId] instanceof Auction)) { return; } - const bidReq = auctions[auctionId].findReqBid('bidId', bidId); + const bidReq = auctions[auctionId].findReqBid(bidId); if (!(bidReq instanceof Bid) || bidReq.used) { return; } @@ -711,6 +748,7 @@ function bidWonHandler(bid) { }).send(); return; } + bidObj.latestAcid = bid.latestTargetedAuctionId; auctions[auctionId].bidWonTime = Date.now(); bidObj.winner = 1; sendEvent(auctionId, adUnitCode, LOG_TYPE.RA, bidObj.adId); @@ -750,6 +788,43 @@ function getCommonLoggingData(acid, adtag) { return Object.assign(commonParams, adunitParams, auctionParams); } +function getResponseSizeMap(acid, adtag) { + const responses = auctions[acid].getAdSlotBidResponses(adtag); + const receivedResponse = {}; + // Set true in map for success bids + responses.filter((bid) => bid.size !== '') + .forEach((bid) => deepSetValue(receivedResponse, `${bid.bidId}.${bid.size}`, true)); + + // For non-success bids: + // 1) set bid.res_sizes = (sizes for which no successful bid received) + // 2) set true in map + responses.filter((bid) => bid.size === '').forEach((bid) => { + bid.res_sizes = bid.allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bid.bidId}.${size}`)); + bid.allMediaTypeSizes.forEach((size) => deepSetValue(receivedResponse, `${bid.bidId}.${size}`, true)); + }); + return receivedResponse; +} + +function getDummyBids(acid, adtag, receivedResponse) { + const emptyBids = []; + auctions[acid].getAdSlotBidRequests(adtag).forEach(({ bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, status }) => { + const emptySizes = allMediaTypeSizes.filter((size) => !deepAccess(receivedResponse, `${bidId}.${size}`)); + if (bidder !== DUMMY_BIDDER && emptySizes.length > 0) { + const bid = new Bid(bidId, bidder, src, start, adUnitCode, mediaType, allMediaTypeSizes, emptySizes); + bid.status = status === BID_SUCCESS ? BID_NOBID : status; + emptyBids.push(bid); + } + }); + return emptyBids; +} + +function getLoggingBids(acid, adtag) { + const receivedResponse = getResponseSizeMap(acid, adtag); + const dummyBids = getDummyBids(acid, adtag, receivedResponse); + + return [...auctions[acid].getAdSlotBidResponses(adtag), ...dummyBids]; +} + function fireAuctionLog(acid, adtag, logType, adId) { let commonParams = getCommonLoggingData(acid, adtag); commonParams.lgtp = logType; @@ -767,7 +842,7 @@ function fireAuctionLog(acid, adtag, logType, adId) { bidParams = [winLogData]; commonParams.lper = 1; } else { - bidParams = auctions[acid].getAdSlotBids(adtag).map(({winner, ...restParams}) => restParams); + bidParams = getLoggingBids(acid, adtag).map((bid) => bid.getLoggingData()) delete commonParams.wts; } let mnetPresent = bidParams.filter(b => b.pvnm === MEDIANET_BIDDER_CODE).length > 0; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index c2c792ec866..5adf3f743a7 100644 --- a/modules/medianetBidAdapter.js +++ b/modules/medianetBidAdapter.js @@ -1,6 +1,6 @@ import { - buildUrl, deepAccess, + formatQS, getWindowTop, isArray, isEmpty, @@ -8,7 +8,9 @@ import { isStr, logError, logInfo, - triggerPixel + safeJSONEncode, + deepClone, + deepSetValue } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; @@ -18,6 +20,8 @@ import {Renderer} from '../src/Renderer.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import {ajax} from '../src/ajax.js'; +import {getViewportCoordinates} from '../libraries/viewport/viewport.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -35,27 +39,20 @@ const SLOT_VISIBILITY = { ABOVE_THE_FOLD: 1, BELOW_THE_FOLD: 2 }; -const EVENTS = { +export const EVENTS = { TIMEOUT_EVENT_NAME: 'client_timeout', - BID_WON_EVENT_NAME: 'client_bid_won' + BID_WON_EVENT_NAME: 'client_bid_won', + SET_TARGETING: 'client_set_targeting', + BIDDER_ERROR: 'client_bidder_error' }; -const EVENT_PIXEL_URL = 'qsearch-a.akamaihd.net/log'; +export const EVENT_PIXEL_URL = 'https://navvy.media.net/log'; const OUTSTREAM = 'outstream'; -// TODO: this should be picked from bidderRequest -let refererInfo = getRefererInfo(); - -let mnData = {}; +let pageMeta; window.mnet = window.mnet || {}; window.mnet.queue = window.mnet.queue || []; -mnData.urlData = { - domain: refererInfo.domain, - page: refererInfo.page, - isTop: refererInfo.reachedTop -}; - const aliases = [ { code: TRUSTEDSTACK_CODE, gvlid: 1288 }, ]; @@ -85,20 +82,20 @@ function siteDetails(site, bidderRequest) { } function getPageMeta() { - if (mnData.pageMeta) { - return mnData.pageMeta; + if (pageMeta) { + return pageMeta; } let canonicalUrl = getUrlFromSelector('link[rel="canonical"]', 'href'); let ogUrl = getUrlFromSelector('meta[property="og:url"]', 'content'); let twitterUrl = getUrlFromSelector('meta[name="twitter:url"]', 'content'); - mnData.pageMeta = Object.assign({}, + pageMeta = Object.assign({}, canonicalUrl && { 'canonical_url': canonicalUrl }, ogUrl && { 'og_url': ogUrl }, twitterUrl && { 'twitter_url': twitterUrl } ); - return mnData.pageMeta; + return pageMeta; } function getUrlFromSelector(selector, attribute) { @@ -184,9 +181,10 @@ function extParams(bidRequest, bidderRequests) { const gdprApplies = !!(gdpr && gdpr.gdprApplies); const uspApplies = !!(uspConsent); const coppaApplies = !!(config.getConfig('coppa')); + const {top = -1, right = -1, bottom = -1, left = -1} = getViewportCoordinates(); return Object.assign({}, { customer_id: params.cid }, - { prebid_version: getGlobal().version }, + { prebid_version: 'v' + '$prebid.version$' }, { gdpr_applies: gdprApplies }, (gdprApplies) && { gdpr_consent_string: gdpr.consentString || '' }, { usp_applies: uspApplies }, @@ -195,11 +193,17 @@ function extParams(bidRequest, bidderRequests) { windowSize.w !== -1 && windowSize.h !== -1 && { screen: windowSize }, userId && { user_id: userId }, getGlobal().medianetGlobals.analyticsEnabled && { analytics: true }, - !isEmpty(sChain) && {schain: sChain} + !isEmpty(sChain) && {schain: sChain}, + { + vcoords: { + top_left: { x: left, y: top }, + bottom_right: { x: right, y: bottom } + } + } ); } -function slotParams(bidRequest) { +function slotParams(bidRequest, bidderRequests) { // check with Media.net Account manager for bid floor and crid parameters let params = { id: bidRequest.bidId, @@ -261,7 +265,9 @@ function slotParams(bidRequest) { if (floorInfo && floorInfo.length > 0) { params.bidfloors = floorInfo; } - + if (bidderRequests.paapi?.enabled) { + params.ext.ae = bidRequest?.ortb2Imp?.ext?.ae; + } return params; } @@ -285,7 +291,7 @@ function getBidFloorByType(bidRequest) { return floorInfo; } function setFloorInfo(bidRequest, mediaType, size, floorInfo) { - let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size}); + let floor = bidRequest.getFloor({currency: 'USD', mediaType: mediaType, size: size}) || {}; if (size.length > 1) floor.size = size; floor.mediaType = mediaType; floorInfo.push(floor); @@ -319,14 +325,15 @@ function getOverlapArea(topLeft1, bottomRight1, topLeft2, bottomRight2) { } function normalizeCoordinates(coordinates) { + const {scrollX, scrollY} = window; return { top_left: { - x: coordinates.top_left.x + window.pageXOffset, - y: coordinates.top_left.y + window.pageYOffset, + x: coordinates.top_left.x + scrollX, + y: coordinates.top_left.y + scrollY, }, bottom_right: { - x: coordinates.bottom_right.x + window.pageXOffset, - y: coordinates.bottom_right.y + window.pageYOffset, + x: coordinates.bottom_right.x + scrollX, + y: coordinates.bottom_right.y + scrollY, } } } @@ -336,14 +343,23 @@ function getBidderURL(bidderCode, cid) { return url + '?cid=' + encodeURIComponent(cid); } +function ortb2Data(ortb2, bidRequests) { + const ortb2Object = deepClone(ortb2); + const eids = deepAccess(bidRequests, '0.userIdAsEids'); + if (eids) { + deepSetValue(ortb2Object, 'user.ext.eids', eids) + } + return ortb2Object; +} + function generatePayload(bidRequests, bidderRequests) { return { site: siteDetails(bidRequests[0].params.site, bidderRequests), ext: extParams(bidRequests[0], bidderRequests), // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 id: bidRequests[0].auctionId, - imp: bidRequests.map(request => slotParams(request)), - ortb2: bidderRequests.ortb2, + imp: bidRequests.map(request => slotParams(request, bidderRequests)), + ortb2: ortb2Data(bidderRequests.ortb2, bidRequests), tmax: bidderRequests.timeout } } @@ -361,38 +377,71 @@ function fetchCookieSyncUrls(response) { return []; } -function getLoggingData(event, data) { - data = (isArray(data) && data) || []; - - let params = {}; +function getEventData(event) { + const params = {}; + const referrerInfo = getRefererInfo(); params.logid = 'kfk'; params.evtid = 'projectevents'; params.project = 'prebid'; - params.acid = deepAccess(data, '0.auctionId') || ''; + params.pbver = '$prebid.version$'; params.cid = getGlobal().medianetGlobals.cid || ''; - params.crid = data.map((adunit) => deepAccess(adunit, 'params.0.crid') || adunit.adUnitCode).join('|'); - params.adunit_count = data.length || 0; - params.dn = mnData.urlData.domain || ''; - params.requrl = mnData.urlData.page || ''; - params.istop = mnData.urlData.isTop || ''; + params.dn = encodeURIComponent(referrerInfo.domain || ''); + params.requrl = encodeURIComponent(referrerInfo.page || ''); params.event = event.name || ''; params.value = event.value || ''; params.rd = event.related_data || ''; + return params; +} +function getBidData(bid) { + const params = {}; + params.acid = bid.auctionId || ''; + params.crid = deepAccess(bid, 'params.crid') || deepAccess(bid, 'params.0.crid') || bid.adUnitCode || ''; + params.ext = safeJSONEncode(bid.ext) || ''; + + const rawobj = deepClone(bid); + delete rawobj.ad; + delete rawobj.vastXml; + params.rawobj = safeJSONEncode(rawobj); return params; } -function logEvent (event, data) { - let getParams = { - protocol: 'https', - hostname: EVENT_PIXEL_URL, - search: getLoggingData(event, data) - }; - triggerPixel(buildUrl(getParams)); +function getLoggingData(event, bids) { + const logData = {}; + if (!isArray(bids)) { + bids = []; + } + bids.forEach((bid) => { + let bidData = getBidData(bid); + Object.keys(bidData).forEach((key) => { + logData[key] = logData[key] || []; + logData[key].push(encodeURIComponent(bidData[key])); + }); + }); + return Object.assign({}, getEventData(event), logData) +} + +function fireAjaxLog(url, payload) { + ajax(url, + { + success: () => undefined, + error: () => undefined + }, + payload, + { + method: 'POST', + keepalive: true + } + ); } -function clearMnData() { - mnData = {}; +function logEvent(event, data) { + const logData = getLoggingData(event, data); + fireAjaxLog(EVENT_PIXEL_URL, formatQS(logData)); +} + +function clearPageMeta() { + pageMeta = undefined; } function addRenderer(bid) { @@ -481,26 +530,33 @@ export const spec = { * 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. + * @returns {{bids: *[], fledgeAuctionConfigs: *[]} | *[]} An object containing bids and fledgeAuctionConfigs if present, otherwise an array of bids. */ interpretResponse: function(serverResponse, request) { let validBids = []; - if (!serverResponse || !serverResponse.body) { logInfo(`${BIDDER_CODE} : response is empty`); return validBids; } - let bids = serverResponse.body.bidList; if (!isArray(bids) || bids.length === 0) { logInfo(`${BIDDER_CODE} : no bids`); + } else { + validBids = bids.filter(bid => isValidBid(bid)); + validBids.forEach(addRenderer); + } + const fledgeAuctionConfigs = deepAccess(serverResponse, 'body.ext.paApiAuctionConfigs') || []; + const ortbAuctionConfigs = deepAccess(serverResponse, 'body.ext.igi') || []; + if (fledgeAuctionConfigs.length === 0 && ortbAuctionConfigs.length === 0) { return validBids; } - validBids = bids.filter(bid => isValidBid(bid)); - - validBids.forEach(addRenderer); - - return validBids; + if (ortbAuctionConfigs.length > 0) { + fledgeAuctionConfigs.push(...ortbAuctionConfigs.map(({igs}) => igs || []).flat()); + } + return { + bids: validBids, + paapi: fledgeAuctionConfigs, + } }, getUserSyncs: function(syncOptions, serverResponses) { let cookieSyncUrls = fetchCookieSyncUrls(serverResponses); @@ -541,7 +597,30 @@ export const spec = { } catch (e) {} }, - clearMnData, + onSetTargeting: (bid) => { + try { + let eventData = { + name: EVENTS.SET_TARGETING, + value: bid.cpm + }; + const enableSendAllBids = config.getConfig('enableSendAllBids'); + if (!enableSendAllBids) { + logEvent(eventData, [bid]); + } + } catch (e) {} + }, + + onBidderError: ({error, bidderRequest}) => { + try { + let eventData = { + name: EVENTS.BIDDER_ERROR, + related_data: `timedOut:${error.timedOut}|status:${error.status}|message:${error.reason.message}` + }; + logEvent(eventData, bidderRequest.bids); + } catch (e) {} + }, + + clearPageMeta, getWindowSize, }; diff --git a/modules/medianetBidAdapter.md b/modules/medianetBidAdapter.md index fbe967122e9..500c9f3f12b 100644 --- a/modules/medianetBidAdapter.md +++ b/modules/medianetBidAdapter.md @@ -180,4 +180,24 @@ var adUnits = [{ }]; +``` + +# Protected Audience API (FLEDGE) + +In order to enable PAAPI auctions follow the instructions below: + +1. Add the paapiForGpt and paapi modules to your prebid bundle. +2. Add the following configuration for the module +``` +pbjs.que.push(function() { + pbjs.setConfig({ + paapi: { + enabled: true, + bidders: ['medianet'], + defaultForSlots: 1 + } + }); +}); +``` +For a detailed guide to enabling PAAPI auctions follow Prebid's documentation on [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) diff --git a/modules/medianetRtdProvider.js b/modules/medianetRtdProvider.js index 5a159b39081..42fcffbf576 100644 --- a/modules/medianetRtdProvider.js +++ b/modules/medianetRtdProvider.js @@ -3,6 +3,7 @@ import {loadExternalScript} from '../src/adloader.js'; import {submodule} from '../src/hook.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {includes} from '../src/polyfill.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; const MODULE_NAME = 'medianet'; const SOURCE = MODULE_NAME + 'rtd'; @@ -84,7 +85,7 @@ function executeCommand(command) { function loadRtdScript(customerId) { const url = getClientUrl(customerId, window.location.hostname); - loadExternalScript(url, MODULE_NAME) + loadExternalScript(url, MODULE_TYPE_RTD, MODULE_NAME) } function getAdUnits(adUnits, adUnitCodes) { diff --git a/modules/mediasniperBidAdapter.js b/modules/mediasniperBidAdapter.js index 5cf0ceaba18..796a15e1778 100644 --- a/modules/mediasniperBidAdapter.js +++ b/modules/mediasniperBidAdapter.js @@ -7,6 +7,7 @@ import { isEmpty, isFn, isNumber, + isPlainObject, isStr, logError, logMessage, @@ -262,7 +263,7 @@ function getFloor(bid, mediaType, size = '*') { size, }); - return !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY + return isPlainObject(floor) && !isNaN(floor.floor) && floor.currency === DEFAULT_CURRENCY ? floor.floor : false; } diff --git a/modules/mgidBidAdapter.js b/modules/mgidBidAdapter.js index fb3990e97f1..a384b9d676c 100644 --- a/modules/mgidBidAdapter.js +++ b/modules/mgidBidAdapter.js @@ -8,18 +8,20 @@ import { parseUrl, isEmpty, triggerPixel, + triggerNurlWithCpm, logWarn, isFn, isNumber, isBoolean, - isInteger, deepSetValue, getBidIdParameter, + extractDomainFromHost, + isInteger, deepSetValue, getBidIdParameter, setOnAny } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import {USERSYNC_DEFAULT_CONFIG} from '../src/userSync.js'; +import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -86,7 +88,7 @@ _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_ID_TO_KEY_MAP[anAsset.ID] = anAs _each(NATIVE_ASSETS, anAsset => { _NATIVE_ASSET_KEY_TO_ASSET_MAP[anAsset.KEY] = anAsset }); export const spec = { - VERSION: '1.6', + VERSION: '1.8', code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, NATIVE], @@ -167,6 +169,8 @@ export const spec = { tagid, secure, }; + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid'); + gpid && isStr(gpid) && deepSetValue(impObj, `ext.gpid`, gpid); const floorData = getBidFloor(bid, cur); if (floorData.floor) { impObj.bidfloor = floorData.floor; @@ -229,7 +233,7 @@ export const spec = { const page = deepAccess(bidderRequest, 'refererInfo.page') || info.location if (!isStr(deepAccess(request.site, 'domain'))) { const hostname = parseUrl(page).hostname; - request.site.domain = extractDomainFromHost(hostname) || hostname + request.site.domain = extractDomainFromHostExceptLocalhost(hostname) || hostname } if (!isStr(deepAccess(request.site, 'page'))) { request.site.page = page @@ -342,13 +346,7 @@ export const spec = { }, onBidWon: (bid) => { const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace( - /\${AUCTION_PRICE}/, - cpm - ); - triggerPixel(bid.nurl); - } + triggerNurlWithCpm(bid, cpm) if (bid.isBurl) { if (bid.mediaType === BANNER) { bid.ad = bid.ad.replace( @@ -365,81 +363,11 @@ export const spec = { } logInfo(LOG_INFO_PREFIX + `onBidWon`); }, - getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { - logInfo(LOG_INFO_PREFIX + `getUserSyncs`); - const spb = isPlainObject(config.getConfig('userSync')) && - isNumber(config.getConfig('userSync').syncsPerBidder) - ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; - - if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - let pixels = []; - if (serverResponses && - isArray(serverResponses) && - serverResponses.length > 0 && - isPlainObject(serverResponses[0].body) && - isPlainObject(serverResponses[0].body.ext) && - isArray(serverResponses[0].body.ext.cm) && - serverResponses[0].body.ext.cm.length > 0) { - pixels = serverResponses[0].body.ext.cm; - } - - const syncs = []; - const query = []; - query.push('cbuster={cbuster}'); - query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); - if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { - query.push('gdpr=1'); - } else { - query.push('gdpr=0'); - } - if (isPlainObject(uspConsent) && uspConsent?.consentString) { - query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); - } - if (isPlainObject(gppConsent) && gppConsent?.gppString) { - query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); - } - if (config.getConfig('coppa')) { - query.push('coppa=1') - } - const q = query.join('&') - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: 'https://cm.mgid.com/i.html?' + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } else if (syncOptions.pixelEnabled) { - if (pixels.length === 0) { - for (let i = 0; i < spb; i++) { - syncs.push({ - type: 'image', - url: 'https://cm.mgid.com/i.gif?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required - }); - } - } else { - for (let i = 0; i < spb && i < pixels.length; i++) { - syncs.push({ - type: 'image', - url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } - } - } - return syncs; - } - } + getUserSyncs: getUserSyncs, }; registerBidder(spec); -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - /** * Unpack the Server's Bid into a Prebid-compatible one. * @param serverBid @@ -486,25 +414,11 @@ function setMediaType(bid, newBid) { } } -function extractDomainFromHost(pageHost) { +function extractDomainFromHostExceptLocalhost(pageHost) { if (pageHost === 'localhost') { return 'localhost' } - let domain = null; - try { - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } - } catch (e) { - domain = null; - } - return domain; + return extractDomainFromHost(pageHost) } function getLanguage() { @@ -682,33 +596,25 @@ function commonNativeRequestObject(nativeAsset, params) { function parseNativeResponse(bid, newBid) { newBid.native = {}; if (bid.hasOwnProperty('adm')) { - let adm = ''; + let nativeAdm = ''; try { - adm = JSON.parse(bid.adm); + nativeAdm = JSON.parse(bid.adm); } catch (ex) { logWarn(LOG_WARN_PREFIX + 'Error: Cannot parse native response for ad response: ' + newBid.adm); return; } - if (adm && adm.native && adm.native.assets && adm.native.assets.length > 0) { + if (nativeAdm && nativeAdm.native && nativeAdm.native.assets && nativeAdm.native.assets.length > 0) { newBid.mediaType = NATIVE; - for (let i = 0, len = adm.native.assets.length; i < len; i++) { - switch (adm.native.assets[i].id) { + for (let i = 0, len = nativeAdm.native.assets.length; i < len; i++) { + switch (nativeAdm.native.assets[i].id) { case NATIVE_ASSETS.TITLE.ID: - newBid.native.title = adm.native.assets[i].title && adm.native.assets[i].title.text; + newBid.native.title = nativeAdm.native.assets[i].title && nativeAdm.native.assets[i].title.text; break; case NATIVE_ASSETS.IMAGE.ID: - newBid.native.image = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; + newBid.native.image = copyFromAdmAsset(nativeAdm.native.assets[i]); break; case NATIVE_ASSETS.ICON.ID: - newBid.native.icon = { - url: adm.native.assets[i].img && adm.native.assets[i].img.url, - height: adm.native.assets[i].img && adm.native.assets[i].img.h, - width: adm.native.assets[i].img && adm.native.assets[i].img.w, - }; + newBid.native.icon = copyFromAdmAsset(nativeAdm.native.assets[i]); break; case NATIVE_ASSETS.SPONSOREDBY.ID: case NATIVE_ASSETS.SPONSORED.ID: @@ -718,14 +624,14 @@ function parseNativeResponse(bid, newBid) { case NATIVE_ASSETS.BODY.ID: case NATIVE_ASSETS.DISPLAYURL.ID: case NATIVE_ASSETS.CTA.ID: - newBid.native[spec.NATIVE_ASSET_ID_TO_KEY_MAP[adm.native.assets[i].id]] = adm.native.assets[i].data && adm.native.assets[i].data.value; + newBid.native[spec.NATIVE_ASSET_ID_TO_KEY_MAP[nativeAdm.native.assets[i].id]] = nativeAdm.native.assets[i].data && nativeAdm.native.assets[i].data.value; break; } } - newBid.native.clickUrl = adm.native.link && adm.native.link.url; - newBid.native.clickTrackers = (adm.native.link && adm.native.link.clicktrackers) || []; - newBid.native.impressionTrackers = adm.native.imptrackers || []; - newBid.native.jstracker = adm.native.jstracker || []; + newBid.native.clickUrl = nativeAdm.native.link && nativeAdm.native.link.url; + newBid.native.clickTrackers = (nativeAdm.native.link && nativeAdm.native.link.clicktrackers) || []; + newBid.native.impressionTrackers = nativeAdm.native.imptrackers || []; + newBid.native.jstracker = nativeAdm.native.jstracker || []; newBid.width = 0; newBid.height = 0; } @@ -785,3 +691,11 @@ function getBidFloor(bid, cur) { } return {floor: bidFloor, cur: cur} } + +function copyFromAdmAsset(asset) { + return { + url: asset.img && asset.img.url, + height: asset.img && asset.img.h, + width: asset.img && asset.img.w, + } +} diff --git a/modules/mgidXBidAdapter.js b/modules/mgidXBidAdapter.js index f073fb4c576..f15b76818b5 100644 --- a/modules/mgidXBidAdapter.js +++ b/modules/mgidXBidAdapter.js @@ -1,277 +1,35 @@ -import { - isFn, - deepAccess, - logMessage, - logError, - isPlainObject, - isNumber, - isArray, - isStr -} from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { USERSYNC_DEFAULT_CONFIG } from '../src/userSync.js'; +import { isBidRequestValid, buildRequestsBase, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; +import { getUserSyncs } from '../libraries/mgidUtils/mgidUtils.js' const BIDDER_CODE = 'mgidX'; const GVLID = 358; const AD_URL = 'https://#{REGION}#.mgid.com/pbjs'; -const PIXEL_SYNC_URL = 'https://cm.mgid.com/i.gif'; -const IFRAME_SYNC_URL = 'https://cm.mgid.com/i.html'; - -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const request = buildRequestsBase({ adUrl: AD_URL, validBidRequests, bidderRequest }); + const region = validBidRequests[0].params?.region; -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); + if (region === 'eu') { + request.url = AD_URL.replace('#{REGION}#', 'eu-x'); + } else { + request.url = AD_URL.replace('#{REGION}#', 'us-east-x'); } - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} + return request; +}; export const spec = { code: BIDDER_CODE, gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests, + interpretResponse, - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - placements.push(getPlacementReqData(bid)); - } - - const region = validBidRequests[0].params?.region; - - let url; - if (region === 'eu') { - url = AD_URL.replace('#{REGION}#', 'eu'); - } else { - url = AD_URL.replace('#{REGION}#', 'us-east-x'); - } - - return { - method: 'POST', - url: 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, gppConsent) => { - const spb = isPlainObject(config.getConfig('userSync')) && - isNumber(config.getConfig('userSync').syncsPerBidder) - ? config.getConfig('userSync').syncsPerBidder : USERSYNC_DEFAULT_CONFIG.syncsPerBidder; - - if (spb > 0 && isPlainObject(syncOptions) && (syncOptions.iframeEnabled || syncOptions.pixelEnabled)) { - let pixels = []; - if (serverResponses && - isArray(serverResponses) && - serverResponses.length > 0 && - isPlainObject(serverResponses[0].body) && - isPlainObject(serverResponses[0].body.ext) && - isArray(serverResponses[0].body.ext.cm) && - serverResponses[0].body.ext.cm.length > 0) { - pixels = serverResponses[0].body.ext.cm; - } - - const syncs = []; - const query = []; - query.push('cbuster={cbuster}'); - query.push('gdpr_consent=' + encodeURIComponent(isPlainObject(gdprConsent) && isStr(gdprConsent?.consentString) ? gdprConsent.consentString : '')); - if (isPlainObject(gdprConsent) && typeof gdprConsent?.gdprApplies === 'boolean' && gdprConsent.gdprApplies) { - query.push('gdpr=1'); - } else { - query.push('gdpr=0'); - } - if (isPlainObject(uspConsent) && uspConsent?.consentString) { - query.push(`us_privacy=${encodeURIComponent(uspConsent?.consentString)}`); - } - if (isPlainObject(gppConsent) && gppConsent?.gppString) { - query.push(`gppString=${encodeURIComponent(gppConsent?.gppString)}`); - } - if (config.getConfig('coppa')) { - query.push('coppa=1') - } - const q = query.join('&') - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: IFRAME_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } else if (syncOptions.pixelEnabled) { - if (pixels.length === 0) { - for (let i = 0; i < spb; i++) { - syncs.push({ - type: 'image', - url: PIXEL_SYNC_URL + '?' + q.replace('{cbuster}', Math.round(new Date().getTime())) // randomly selects partner if sync required - }); - } - } else { - for (let i = 0; i < spb && i < pixels.length; i++) { - syncs.push({ - type: 'image', - url: pixels[i] + (pixels[i].indexOf('?') > 0 ? '&' : '?') + q.replace('{cbuster}', Math.round(new Date().getTime())) - }); - } - } - } - return syncs; - } - } + getUserSyncs: getUserSyncs, }; registerBidder(spec); diff --git a/modules/microadBidAdapter.js b/modules/microadBidAdapter.js index 61aa9b795de..82b9025766b 100644 --- a/modules/microadBidAdapter.js +++ b/modules/microadBidAdapter.js @@ -28,7 +28,6 @@ const AUDIENCE_IDS = [ {type: 8, bidKey: 'userId.id5id.uid', source: 'id5-sync.com'}, {type: 9, bidKey: 'userId.tdid', source: 'adserver.org'}, {type: 10, bidKey: 'userId.novatiq.snowflake', source: 'novatiq.com'}, - {type: 11, bidKey: 'userId.parrableId.eid', source: 'parrable.com'}, {type: 12, bidKey: 'userId.dacId.id', source: 'dac.co.jp'}, {type: 13, bidKey: 'userId.idl_env', source: 'liveramp.com'}, {type: 14, bidKey: 'userId.criteoId', source: 'criteo.com'}, diff --git a/modules/minutemediaBidAdapter.js b/modules/minutemediaBidAdapter.js index d14af07210e..4e83c5c6db4 100644 --- a/modules/minutemediaBidAdapter.js +++ b/modules/minutemediaBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'minutemedia'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.minutemedia-prebid.com/'; +const BASE_URL = 'https://hb.minutemedia-prebid.com/'; const MODES = { PRODUCTION: 'hb-mm-multi', TEST: 'hb-multi-mm-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -51,50 +46,24 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const combinedRequestsObject = {}; - // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; - combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject - } + }; }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -104,20 +73,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel - } - }) - syncs.push(...pixels) + }; + }); + syncs.push(...pixels); } } return syncs; @@ -135,349 +104,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(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 !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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && 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'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams -} diff --git a/modules/minutemediaplusBidAdapter.js b/modules/minutemediaplusBidAdapter.js deleted file mode 100644 index 146d437b1fa..00000000000 --- a/modules/minutemediaplusBidAdapter.js +++ /dev/null @@ -1,349 +0,0 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} 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 {config} from '../src/config.js'; - -const GVLID = 918; -const DEFAULT_SUB_DOMAIN = 'exchange'; -const BIDDER_CODE = 'mmplus'; -const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; - -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - -export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { - return `https://${subDomain}.minutemedia-prebid.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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - auctionId, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: auctionId, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.minutemedia-prebid.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.minutemedia-prebid.com/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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, VIDEO], - isBidRequestValid, - buildRequests, - interpretResponse, - getUserSyncs -}; - -registerBidder(spec); diff --git a/modules/missenaBidAdapter.js b/modules/missenaBidAdapter.js index 99cad1c7bc6..e621e478ce7 100644 --- a/modules/missenaBidAdapter.js +++ b/modules/missenaBidAdapter.js @@ -11,12 +11,15 @@ import { config } from '../src/config.js'; import { BANNER } from '../src/mediaTypes.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; +import { isAutoplayEnabled } from '../libraries/autoplayDetection/autoplay.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').BidderRequest} BidderRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ const BIDDER_CODE = 'missena'; @@ -38,11 +41,59 @@ function getFloor(bidRequest) { mediaType: BANNER, }); - if (!isNaN(bidFloors.floor)) { + if (!isNaN(bidFloors?.floor)) { return bidFloors; } } +/* Helper function that converts the prebid data to the payload expected by our servers */ +function toPayload(bidRequest, bidderRequest) { + const payload = { + adunit: bidRequest.adUnitCode, + ik: window.msna_ik, + request_id: bidRequest.bidId, + timeout: bidderRequest.timeout, + }; + + if (bidderRequest && bidderRequest.refererInfo) { + // TODO: is 'topmostLocation' the right value here? + payload.referer = bidderRequest.refererInfo.topmostLocation; + payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; + } + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.consent_string = bidderRequest.gdprConsent.consentString; + payload.consent_required = bidderRequest.gdprConsent.gdprApplies; + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent; + } + + const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; + payload.params = bidRequest.params; + + if (bidRequest.ortb2?.device?.ext?.cdep) { + payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; + } + payload.userEids = bidRequest.userIdAsEids || []; + payload.version = '$prebid.version$'; + + const bidFloor = getFloor(bidRequest); + payload.floor = bidFloor?.floor; + payload.floor_currency = bidFloor?.currency; + payload.currency = config.getConfig('currency.adServerCurrency'); + payload.schain = bidRequest.schain; + payload.coppa = bidderRequest?.ortb2?.regs?.coppa ? 1 : 0; + payload.autoplay = isAutoplayEnabled() === true ? 1 : 0; + + return { + method: 'POST', + url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), + data: JSON.stringify(payload), + }; +} + export const spec = { aliases: ['msna'], code: BIDDER_CODE, @@ -62,7 +113,8 @@ export const spec = { /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {Array} validBidRequests + * @param {BidderRequest} bidderRequest * @return ServerRequest Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { @@ -78,54 +130,9 @@ export const spec = { return []; } - return validBidRequests.map((bidRequest) => { - const payload = { - adunit: bidRequest.adUnitCode, - ik: window.msna_ik, - request_id: bidRequest.bidId, - timeout: bidderRequest.timeout, - }; - - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'topmostLocation' the right value here? - payload.referer = bidderRequest.refererInfo.topmostLocation; - payload.referer_canonical = bidderRequest.refererInfo.canonicalUrl; - } - - if (bidderRequest && bidderRequest.gdprConsent) { - payload.consent_string = bidderRequest.gdprConsent.consentString; - payload.consent_required = bidderRequest.gdprConsent.gdprApplies; - } - const baseUrl = bidRequest.params.baseUrl || ENDPOINT_URL; - if (bidRequest.params.test) { - payload.test = bidRequest.params.test; - } - if (bidRequest.params.placement) { - payload.placement = bidRequest.params.placement; - } - if (bidRequest.params.formats) { - payload.formats = bidRequest.params.formats; - } - if (bidRequest.params.isInternal) { - payload.is_internal = bidRequest.params.isInternal; - } - if (bidRequest.ortb2?.device?.ext?.cdep) { - payload.cdep = bidRequest.ortb2?.device?.ext?.cdep; - } - payload.userEids = bidRequest.userIdAsEids || []; - payload.version = '$prebid.version$'; - - const bidFloor = getFloor(bidRequest); - payload.floor = bidFloor?.floor; - payload.floor_currency = bidFloor?.currency; - payload.currency = config.getConfig('currency.adServerCurrency') || 'EUR'; - - return { - method: 'POST', - url: baseUrl + '?' + formatQS({ t: bidRequest.params.apiKey }), - data: JSON.stringify(payload), - }; - }); + return validBidRequests.map((bidRequest) => + toPayload(bidRequest, bidderRequest), + ); }, /** @@ -170,7 +177,7 @@ export const spec = { }, /** * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data + * @param {TimedOutBid} timeoutData - Containing timeout specific data */ onTimeout: function onTimeout(timeoutData) { logInfo('Missena - Timeout from adapter', timeoutData); @@ -178,7 +185,7 @@ export const spec = { /** * Register bidder specific code, which@ will execute if a bid from this bidder won the auction - * @param {Bid} The bid that won the auction + * @param {Bid} bid - The bid that won the auction */ onBidWon: function (bid) { const hostname = bid.params[0].baseUrl ? EVENTS_DOMAIN_DEV : EVENTS_DOMAIN; diff --git a/modules/mobfoxpbBidAdapter.js b/modules/mobfoxpbBidAdapter.js index 35e9b03c031..dcc6e9594c4 100644 --- a/modules/mobfoxpbBidAdapter.js +++ b/modules/mobfoxpbBidAdapter.js @@ -1,147 +1,17 @@ -import { isFn, deepAccess, getWindowTop } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { isBidRequestValid, buildRequests, interpretResponse } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'mobfoxpb'; const AD_URL = 'https://bes.mobfox.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); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const winTop = 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; - } - - // Add GPP consent - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - } - - 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.traffic = BANNER; - placement.sizes = mediaType[BANNER].sizes; - } else if (mediaType && mediaType[VIDEO] && mediaType[VIDEO].playerSize) { - placement.traffic = VIDEO; - placement.wPlayer = mediaType[VIDEO].playerSize[0]; - placement.hPlayer = mediaType[VIDEO].playerSize[1]; - placement.playerSize = mediaType[VIDEO].playerSize; - 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; - } else if (mediaType && mediaType[NATIVE]) { - placement.traffic = NATIVE; - placement.native = mediaType[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)) { - resItem.meta = resItem.meta || {}; - resItem.meta.advertiserDomains = resItem.adomain || []; - - response.push(resItem); - } - } - return response; - }, + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse }; registerBidder(spec); diff --git a/modules/mobianRtdProvider.js b/modules/mobianRtdProvider.js new file mode 100644 index 00000000000..02f2d1b83cf --- /dev/null +++ b/modules/mobianRtdProvider.js @@ -0,0 +1,203 @@ +/** + * This module adds the Mobian RTD provider to the real time data module + * The {@link module:modules/realTimeData} module is required + */ +import { submodule } from '../src/hook.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { safeJSONParse, logMessage as _logMessage } from '../src/utils.js'; +import { setKeyValue } from '../libraries/gptUtils/gptUtils.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +/** + * @typedef {Object} MobianConfig + * @property {MobianConfigParams} params + */ + +/** + * @typedef {Object} MobianConfigParams + * @property {string} [prefix] - Optional prefix for targeting keys (default: 'mobian') + * @property {boolean} [publisherTargeting] - Optional boolean to enable targeting for publishers (default: false) + * @property {boolean} [advertiserTargeting] - Optional boolean to enable targeting for advertisers (default: false) + */ + +/** + * @typedef {Object} MobianContextData + * @property {Object} apValues + * @property {string[]} categories + * @property {string[]} emotions + * @property {string[]} genres + * @property {string} risk + * @property {string} sentiment + * @property {string[]} themes + * @property {string[]} tones + */ + +export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async'; + +export const CONTEXT_KEYS = [ + 'apValues', + 'categories', + 'emotions', + 'genres', + 'risk', + 'sentiment', + 'themes', + 'tones' +]; + +const AP_KEYS = ['a0', 'a1', 'p0', 'p1']; + +const logMessage = (...args) => { + _logMessage('Mobian', ...args); +}; + +function makeMemoizedFetch() { + let cachedResponse = null; + return async function () { + if (cachedResponse) { + return Promise.resolve(cachedResponse); + } + try { + const response = await fetchContextData(); + cachedResponse = makeDataFromResponse(response); + return cachedResponse; + } catch (error) { + logMessage('error', error); + return Promise.resolve({}); + } + } +} + +export const getContextData = makeMemoizedFetch(); + +export async function fetchContextData() { + const pageUrl = encodeURIComponent(window.location.href); + const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; + const request = ajaxBuilder(); + + return new Promise((resolve, reject) => { + request(requestUrl, { success: resolve, error: reject }); + }); +} + +export function getConfig(config) { + const [advertiserTargeting, publisherTargeting] = ['advertiserTargeting', 'publisherTargeting'].map((key) => { + const value = config?.params?.[key]; + if (!value) { + return []; + } else if (value === true) { + return CONTEXT_KEYS; + } else if (Array.isArray(value) && value.length) { + return value.filter((key) => CONTEXT_KEYS.includes(key)); + } + return []; + }); + + const prefix = config?.params?.prefix || 'mobian'; + return { advertiserTargeting, prefix, publisherTargeting }; +} + +/** + * @param {MobianConfigParams} parsedConfig + * @param {MobianContextData} contextData + * @returns {function} + */ +export function setTargeting(parsedConfig, contextData) { + const { publisherTargeting, prefix } = parsedConfig; + logMessage('context', contextData); + + CONTEXT_KEYS.forEach((key) => { + if (!publisherTargeting.includes(key)) return; + + if (key === 'apValues') { + AP_KEYS.forEach((apKey) => { + if (!contextData[key]?.[apKey]?.length) return; + logMessage(`${prefix}_ap_${apKey}`, contextData[key][apKey]); + setKeyValue(`${prefix}_ap_${apKey}`, contextData[key][apKey]); + }); + return; + } + + if (contextData[key]?.length) { + logMessage(`${prefix}_${key}`, contextData[key]); + setKeyValue(`${prefix}_${key}`, contextData[key]); + } + }); +} + +export function makeDataFromResponse(contextData) { + const data = typeof contextData === 'string' ? safeJSONParse(contextData) : contextData; + const results = data.results; + if (!results) { + return {}; + } + return { + apValues: results.ap || {}, + categories: results.mobianContentCategories, + emotions: results.mobianEmotions, + genres: results.mobianGenres, + risk: results.mobianRisk || 'unknown', + sentiment: results.mobianSentiment || 'unknown', + themes: results.mobianThemes, + tones: results.mobianTones, + }; +} + +export function extendBidRequestConfig(bidReqConfig, contextData) { + logMessage('extendBidRequestConfig', bidReqConfig, contextData); + const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + + ortb2Site.ext = ortb2Site.ext || {}; + ortb2Site.ext.data = { + ...(ortb2Site.ext.data || {}), + ...contextData + }; + + return bidReqConfig; +} + +/** + * @param {MobianConfig} config + * @returns {boolean} + */ +function init(config) { + logMessage('init', config); + + const parsedConfig = getConfig(config); + + if (parsedConfig.publisherTargeting.length) { + getContextData().then((contextData) => setTargeting(parsedConfig, contextData)); + } + + return true; +} + +function getBidRequestData(bidReqConfig, callback, config) { + logMessage('getBidRequestData', bidReqConfig); + + const { advertiserTargeting } = getConfig(config); + + if (!advertiserTargeting.length) { + callback(); + return; + } + + getContextData() + .then((contextData) => { + extendBidRequestConfig(bidReqConfig, contextData); + }) + .catch(() => {}) + .finally(() => callback()); +} + +/** @type {RtdSubmodule} */ +export const mobianBrandSafetySubmodule = { + name: 'mobianBrandSafety', + init: init, + getBidRequestData: getBidRequestData +}; + +submodule('realTimeData', mobianBrandSafetySubmodule); diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md new file mode 100644 index 00000000000..e974e56dcdd --- /dev/null +++ b/modules/mobianRtdProvider.md @@ -0,0 +1,41 @@ +# Mobian Rtd Provider + +## Overview + +Module Name: Mobian Rtd Provider +Module Type: Rtd Provider +Maintainer: rich.rodriguez@themobian.com + +## Description + +RTD provider for themobian Brand Safety determinations. Publishers +should use this to get Mobian's GARM Risk evaluations for +a URL. + +## Configuration + +```js +pbjs.setConfig({ + realTimeData: { + dataProviders: [{ + name: 'mobianBrandSafety', + params: { + // Prefix for the targeting keys (default: 'mobian') + prefix: 'mobian', + + // Enable targeting keys for advertiser data + advertiserTargeting: true, + // Or set it as an array to pick specific targeting keys: + // advertiserTargeting: ['genres', 'emotions', 'themes'], + // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' + + // Enable targeting keys for publisher data + publisherTargeting: true, + // Or set it as an array to pick specific targeting keys: + // publisherTargeting: ['tones', 'risk'], + // Available values: 'apValues', 'categories', 'emotions', 'genres', 'risk', 'sentiment', 'themes', 'tones' + } + }] + } +}); +``` diff --git a/modules/mytargetBidAdapter.md b/modules/mytargetBidAdapter.md deleted file mode 100644 index 3292ff561fa..00000000000 --- a/modules/mytargetBidAdapter.md +++ /dev/null @@ -1,40 +0,0 @@ -# Overview - -``` -Module Name: myTarget Bidder Adapter -Module Type: Bidder Adapter -Maintainer: support_target@corp.my.com -``` - -# Description - -Module that connects to myTarget demand sources. - -# Test Parameters - -``` - var adUnits = [{ - code: 'placementCode', - mediaTypes: { - banner: { - sizes: [[240, 400]], - } - }, - bids: [{ - bidder: 'mytarget', - params: { - placementId: '379783', - - // OPTIONAL: custom bid floor - bidfloor: 10000, - - // OPTIONAL: if you know the ad position on the page, specify it here - // (this corresponds to "Ad Position" in OpenRTB 2.3, section 5.4) - position: 0, - - // OPTIONAL: bid response type: 0 - ad url (default), 1 - ad markup - response: 0 - } - }] - }]; -``` diff --git a/modules/nativoBidAdapter.js b/modules/nativoBidAdapter.js index 69a270247cd..bdd0fa7e054 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -1,6 +1,6 @@ import { deepAccess, isEmpty } from '../src/utils.js' import { registerBidder } from '../src/adapters/bidderFactory.js' -import { BANNER } from '../src/mediaTypes.js' +import { BANNER, VIDEO, NATIVE } from '../src/mediaTypes.js' import { getGlobal } from '../src/prebidGlobal.js' import { ortbConverter } from '../libraries/ortbConverter/converter.js' @@ -8,14 +8,16 @@ const converter = ortbConverter({ context: { // `netRevenue` and `ttl` are required properties of bid responses - provide a default for them netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false - ttl: 30 // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) + ttl: 30, // default bidResponse.ttl (when not specified in ORTB response.seatbid[].bid[].exp) }, imp(buildImp, bidRequest, context) { - const imp = buildImp(bidRequest, context); + const imp = buildImp(bidRequest, context) imp.tagid = bidRequest.adUnitCode - return imp; - } -}); + if (imp.ext) imp.ext.placementId = bidRequest.params.placementId + + return imp + }, +}) const BIDDER_CODE = 'nativo' const BIDDER_ENDPOINT = 'https://exchange.postrelease.com/prebid' @@ -24,17 +26,27 @@ const GVLID = 263 const TIME_TO_LIVE = 360 -const SUPPORTED_AD_TYPES = [BANNER] +const SUPPORTED_AD_TYPES = [BANNER, VIDEO, NATIVE] const FLOOR_PRICE_CURRENCY = 'USD' const PRICE_FLOOR_WILDCARD = '*' const localPbjsRef = getGlobal() +function getMediaType(accessObj) { + if (deepAccess(accessObj, 'mediaTypes.video')) { + return VIDEO + } else if (deepAccess(accessObj, 'mediaTypes.native')) { + return NATIVE + } else { + return BANNER + } +} + /** * Keep track of bid data by keys * @returns {Object} - Map of bid data that can be referenced by multiple keys */ -export const BidDataMap = () => { +export function BidDataMap() { const referenceMap = {} const bids = [] @@ -122,8 +134,7 @@ export const spec = { */ isBidRequestValid: function (bid) { // We don't need any specific parameters to make a bid request - // If not parameters are supplied just verify it's the correct bidder code - if (!bid.params) return bid.bidder === BIDDER_CODE + if (!bid.params) return true // Check if any supplied parameters are invalid const hasInvalidParameters = Object.keys(bid.params).some((key) => { @@ -150,7 +161,10 @@ export const spec = { */ buildRequests: function (validBidRequests, bidderRequest) { // Get OpenRTB Data - const openRTBData = converter.toORTB({bidRequests: validBidRequests, bidderRequest}) + const openRTBData = converter.toORTB({ + bidRequests: validBidRequests, + bidderRequest, + }) const openRTBDataString = JSON.stringify(openRTBData) const requestData = new RequestData() @@ -201,7 +215,8 @@ export const spec = { let params = [ // Prebid version { - key: 'ntv_pbv', value: localPbjsRef.version + key: 'ntv_pbv', + value: localPbjsRef.version, }, // Prebid request id { key: 'ntv_pb_rid', value: bidderRequest.bidderRequestId }, @@ -278,19 +293,31 @@ export const spec = { }) } + // Add GPP params + if (bidderRequest.gppConsent) { + params.unshift({ + key: 'ntv_gpp_consent', + value: bidderRequest.gppConsent.gppString, + }) + } + // Add USP params if (bidderRequest.uspConsent) { // Put on the beginning of the qs param array params.unshift({ key: 'us_privacy', value: bidderRequest.uspConsent }) } - const qsParamStrings = [requestData.getRequestDataQueryString(), arrayToQS(params)] + const qsParamStrings = [ + requestData.getRequestDataQueryString(), + arrayToQS(params), + ] const requestUrl = buildRequestUrl(BIDDER_ENDPOINT, qsParamStrings) let serverRequest = { method: 'POST', url: requestUrl, data: openRTBDataString, + bidderRequest: bidderRequest, } return serverRequest @@ -320,9 +347,10 @@ export const spec = { // Step through and grab pertinent data let bidResponse, adUnit - seatbids.forEach((seatbid) => { + seatbids.forEach((seatbid, i) => { seatbid.bid.forEach((bid) => { adUnit = this.getAdUnitData(body.id, bid) + bidResponse = { requestId: adUnit.bidId, cpm: bid.price, @@ -337,10 +365,18 @@ export const spec = { meta: { advertiserDomains: bid.adomain, }, + mediaType: getMediaType(request.bidderRequest.bids[i]), } if (bid.ext) extData[bid.id] = bid.ext - + if (bidResponse.mediaType === VIDEO) { + bidResponse.vastUrl = bid.adm + } + if (bidResponse.mediaType === NATIVE) { + bidResponse.native = { + ortb: JSON.parse(bidResponse.ad), + } + } bidResponses.push(bidResponse) }) }) @@ -414,23 +450,27 @@ export const spec = { typeof response.body === 'string' ? JSON.parse(response.body) : response.body - } catch (err) { return } + } catch (err) { + return + } // 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), - }) + if (seatbid.syncUrls) { + seatbid.syncUrls.forEach((sync) => { + if (types[sync.type]) { + if (sync.url.trim() !== '') { + syncs.push({ + type: sync.type, + url: sync.url.replace('{GDPR_params}', params), + }) + } } - } - }) + }) + } }) }) @@ -491,7 +531,9 @@ export class RequestData { getRequestDataQueryString() { if (this.bidRequestDataSources.length == 0) return - const queryParams = this.bidRequestDataSources.map(dataSource => dataSource.getRequestQueryString()).filter(queryString => queryString !== '') + const queryParams = this.bidRequestDataSources + .map((dataSource) => dataSource.getRequestQueryString()) + .filter((queryString) => queryString !== '') return queryParams.join('&') } } @@ -500,8 +542,10 @@ export class BidRequestDataSource { constructor() { this.type = 'BidRequestDataSource' } - processBidRequestData(bidRequest, bidderRequest) { } - getRequestQueryString() { return '' } + processBidRequestData(bidRequest, bidderRequest) {} + getRequestQueryString() { + return '' + } } export class UserEIDs extends BidRequestDataSource { @@ -540,7 +584,7 @@ QueryStringParam.prototype.toString = function () { export function encodeToBase64(value) { try { return btoa(JSON.stringify(value)) - } catch (err) { } + } catch (err) {} } export function parseFloorPriceData(bidRequest) { @@ -565,7 +609,7 @@ export function parseFloorPriceData(bidRequest) { size, }) // Save the data and track the sizes - mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData.floor + mediaTypeFloorPriceData[sizeToString(size)] = priceFloorData?.floor sizeOptions.add(size) }) bidRequestFloorPriceData[mediaType] = mediaTypeFloorPriceData @@ -573,7 +617,7 @@ export function parseFloorPriceData(bidRequest) { // Get floor price of current media type with a wildcard size const sizeWildcardFloor = getSizeWildcardPrice(bidRequest, mediaType) // Save the wildcard floor price if it was retrieved successfully - if (sizeWildcardFloor.floor > 0) { + if (sizeWildcardFloor?.floor > 0) { mediaTypeFloorPriceData['*'] = sizeWildcardFloor.floor } }) @@ -708,9 +752,13 @@ function getLargestSize(sizes, method = area) { * Build the final request url */ export function buildRequestUrl(baseUrl, qsParamStringArray = []) { - if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) return baseUrl + if (qsParamStringArray.length === 0 || !Array.isArray(qsParamStringArray)) { + return baseUrl + } - const nonEmptyQSParamStrings = qsParamStringArray.filter(qsParamString => qsParamString.trim() !== '') + const nonEmptyQSParamStrings = qsParamStringArray.filter( + (qsParamString) => qsParamString.trim() !== '' + ) if (nonEmptyQSParamStrings.length === 0) return baseUrl @@ -752,7 +800,7 @@ export function getPageUrlFromBidRequest(bidRequest) { try { const url = new URL(paramPageUrl) return url.href - } catch (err) { } + } catch (err) {} } export function hasProtocol(url) { diff --git a/modules/nativoBidAdapter.md b/modules/nativoBidAdapter.md index f83fb45b52e..515d87af28e 100644 --- a/modules/nativoBidAdapter.md +++ b/modules/nativoBidAdapter.md @@ -16,24 +16,91 @@ gulp serve --modules=nativoBidAdapter # Test Parameters +## Banner + +```js +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: { + url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html', + }, + }, + ], + }, +] ``` + +## Video + +```js 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: { - url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html' - } - }] - - } - ]; + { + code: 'ntvPlaceholder-1', + mediaTypes: { + video: { + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6], + playbackmethod: [1, 2], + skip: 1, + skipafter: 5, + }, + }, + video: { + divId: 'player', + }, + bids: [ + { + bidder: 'nativo', + params: { + url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html', + }, + }, + ], + }, +] +``` + +## Native +```js +var adUnits = [ + { + code: '/416881364/prebid-native-test-unit', + sizes: [[300, 250]], + mediaTypes: { + native: { + title: { + required: true, + }, + image: { + required: true, + }, + sponsoredBy: { + required: true, + }, + }, + }, + bids: [ + { + bidder: 'nativo', + params: { + url: 'https://test-sites.internal.nativo.net/testing/prebid_adpater.html', + }, + }, + ], + }, +] ``` diff --git a/modules/naveggIdSystem.js b/modules/naveggIdSystem.js index 42c6b113566..6f1964b11df 100644 --- a/modules/naveggIdSystem.js +++ b/modules/naveggIdSystem.js @@ -6,9 +6,9 @@ */ import { isStr, isPlainObject, logError } from '../src/utils.js'; import { submodule } from '../src/hook.js'; -import { ajax } from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; +import { ajaxBuilder } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -19,61 +19,72 @@ const MODULE_NAME = 'naveggId'; const OLD_NAVEGG_ID = 'nid'; const NAVEGG_ID = 'nvggid'; const BASE_URL = 'https://id.navegg.com/uid/'; -const DEFAULT_EXPIRE = 8 * 24 * 3600 * 1000; -const INVALID_EXPIRE = 3600 * 1000; export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); -function getNaveggIdFromApi() { - const callbacks = { - success: response => { - if (response) { - try { - const responseObj = JSON.parse(response); - writeCookie(NAVEGG_ID, responseObj[NAVEGG_ID]); - } catch (error) { - logError(error); +function getIdFromAPI() { + const resp = function (callback) { + ajaxBuilder()( + BASE_URL, + response => { + if (response) { + let responseObj; + try { + responseObj = JSON.parse(response); + } catch (error) { + logError(error); + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + } + + if (responseObj && responseObj[NAVEGG_ID]) { + callback(responseObj[NAVEGG_ID]); + } else { + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + } } - } - }, - error: error => { - logError('Navegg ID fetch encountered an error', error); - } + }, + error => { + logError('Navegg ID fetch encountered an error', error); + const fallbackValue = getNaveggIdFromLocalStorage() || getOldCookie(); + callback(fallbackValue); + }, + {method: 'GET', withCredentials: false}); }; - ajax(BASE_URL, callbacks, undefined, { method: 'GET', withCredentials: false }); -} - -function writeCookie(key, value) { - try { - if (storage.cookiesAreEnabled) { - let expTime = new Date(); - const expires = value ? DEFAULT_EXPIRE : INVALID_EXPIRE; - expTime.setTime(expTime.getTime() + expires); - storage.setCookie(key, value, expTime.toUTCString(), 'none'); - } - } catch (e) { - logError(e); - } + return resp; } -function readnaveggIdFromLocalStorage() { - return storage.localStorageIsEnabled ? storage.getDataFromLocalStorage(NAVEGG_ID) : null; +/** + * @returns {string | null} + */ +function readNvgIdFromCookie() { + return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nvg') ? storage.findSimilarCookies('nvg')[0] : null) : null; } - -function readnaveggIDFromCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(NAVEGG_ID) : null; +/** + * @returns {string | null} + */ +function readNavIdFromCookie() { + return storage.cookiesAreEnabled() ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; } - -function readoldnaveggIDFromCookie() { - return storage.cookiesAreEnabled ? storage.getCookie(OLD_NAVEGG_ID) : null; +/** + * @returns {string | 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; +/** + * @returns {string | null} + */ +function getOldCookie() { + const oldCookie = readOldNaveggIdFromCookie() || readNvgIdFromCookie() || readNavIdFromCookie(); + return oldCookie; } - -function readnavIDFromCookie() { - return storage.cookiesAreEnabled ? (storage.findSimilarCookies('nav') ? storage.findSimilarCookies('nav')[0] : null) : null; +/** + * @returns {string | null} + */ +function getNaveggIdFromLocalStorage() { + return storage.localStorageIsEnabled() ? storage.getDataFromLocalStorage(NAVEGG_ID) : null; } /** @type {Submodule} */ @@ -95,23 +106,16 @@ export const naveggIdSubmodule = { 'naveggId': naveggIdVal.split('|')[0] } : 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 naveggIdString = readnaveggIdFromLocalStorage() || readnaveggIDFromCookie() || getNaveggIdFromApi() || readoldnaveggIDFromCookie() || readnvgIDFromCookie() || readnavIDFromCookie(); - - if (typeof naveggIdString == 'string' && naveggIdString) { - try { - return { id: naveggIdString }; - } catch (error) { - logError(error); - } - } - return undefined; + getId(config, consentData) { + const resp = getIdFromAPI() + return {callback: resp} }, eids: { 'naveggId': { diff --git a/modules/naveggIdSystem.md b/modules/naveggIdSystem.md index f758fbc9d5d..81d9841c78f 100644 --- a/modules/naveggIdSystem.md +++ b/modules/naveggIdSystem.md @@ -10,6 +10,11 @@ pbjs.setConfig({ userSync: { userIds: [{ name: 'naveggId', + storage: { + name : 'nvggid', + type : 'cookie&html5', + expires: 8 + } }] } }); @@ -20,3 +25,6 @@ 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"` | +| storage.name | Required | String | The name of the cookie or html5 local storage where the user ID will be stored. | `"nvggid"` | +| storage.type | Required | String | Must be "`cookie`", "`html5`" or "`cookie&html5`". This is where the results of the user ID will be stored. | `"cookie&html5"` | +| storage.expires | Required | Integer | How long (in days) the user ID information will be stored. | `8` | diff --git a/modules/newspassidBidAdapter.js b/modules/newspassidBidAdapter.js index 2a4b2da186b..d33b4e64297 100644 --- a/modules/newspassidBidAdapter.js +++ b/modules/newspassidBidAdapter.js @@ -1,4 +1,5 @@ import { + deepClone, logInfo, logError, deepAccess, @@ -33,12 +34,12 @@ export const spec = { }, loadConfiguredData(bid) { if (this.propertyBag.config) { return; } - this.propertyBag.config = JSON.parse(JSON.stringify(this.config_defaults)); + this.propertyBag.config = deepClone(this.config_defaults); let bidder = bid.bidder || 'newspassid'; this.propertyBag.config.logId = bidder.toUpperCase(); this.propertyBag.config.bidder = bidder; let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + logInfo('got bidderConfig: ', deepClone(bidderConfig)); let arrGetParams = this.getGetParametersAsObject(); if (bidderConfig.endpointOverride) { if (bidderConfig.endpointOverride.origin) { @@ -131,7 +132,7 @@ export const spec = { buildRequests(validBidRequests, bidderRequest) { this.loadConfiguredData(validBidRequests[0]); this.propertyBag.buildRequestsStart = new Date().getTime(); - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${NEWSPASSVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); if (this.blockTheRequest()) { return []; } @@ -281,7 +282,7 @@ export const spec = { data: JSON.stringify(npRequest), bidderRequest: bidderRequest }; - logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(npRequest))); + logInfo('buildRequests request data for single = ', deepClone(npRequest)); this.propertyBag.buildRequestsEnd = new Date().getTime(); logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; @@ -309,7 +310,7 @@ export const spec = { if (request && request.bidderRequest && request.bidderRequest.bids) { this.loadConfiguredData(request.bidderRequest.bids[0]); } let startTime = new Date().getTime(); logInfo(`interpretResponse time: ${startTime}. buildRequests done -> interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); serverResponse = serverResponse.body || {}; let aucId = serverResponse.id; // this will be correct for single requests and non-single if (!serverResponse.hasOwnProperty('seatbid')) { @@ -464,10 +465,6 @@ export const spec = { if (id5id) { ret['id5id'] = id5id; } - let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); - if (parrableId) { - ret['parrableId'] = parrableId; - } let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); if (sharedid) { ret['sharedid'] = sharedid; diff --git a/modules/nextMillenniumBidAdapter.js b/modules/nextMillenniumBidAdapter.js index 65f530d9e58..112f0d68504 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,6 +1,5 @@ import { _each, - createTrackPixelHtml, deepAccess, deepSetValue, getBidIdParameter, @@ -13,7 +12,8 @@ import { triggerPixel, } from '../src/utils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js'; + import { EVENTS } from '../src/constants.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -21,7 +21,8 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getRefererInfo} from '../src/refererDetection.js'; -const NM_VERSION = '3.1.0'; +const NM_VERSION = '4.2.1'; +const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; @@ -30,6 +31,7 @@ const SYNC_ENDPOINT = 'https://cookies.nextmillmedia.com/sync?gdpr={{.GDPR}}&gdp const REPORT_ENDPOINT = 'https://report2.hb.brainlyads.com/statistics/metric'; const TIME_TO_LIVE = 360; const DEFAULT_CURRENCY = 'USD'; +const DEFAULT_TMAX = 1500; const VIDEO_PARAMS_DEFAULT = { api: undefined, @@ -65,6 +67,10 @@ const ALLOWED_ORTB2_PARAMETERS = [ 'site.keywords', 'site.content.keywords', 'user.keywords', + 'bcat', + 'badv', + 'wlang', + 'wlangb', ]; export const spec = { @@ -74,91 +80,76 @@ export const spec = { isBidRequestValid: function(bid) { return !!( - (bid.params.placement_id && isStr(bid.params.placement_id)) || (bid.params.group_id && isStr(bid.params.group_id)) + (bid.params.placement_id && isStr(bid.params.placement_id)) || + (bid.params.group_id && isStr(bid.params.group_id)) ); }, buildRequests: function(validBidRequests, bidderRequest) { const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; + const site = getSiteObj(); + const device = getDeviceObj(); + const source = getSourceObj(validBidRequests, bidderRequest); + const tmax = deepAccess(bidderRequest, 'timeout') || DEFAULT_TMAX; + + const postBody = { + id: bidderRequest?.bidderRequestId, + tmax, + ext: { + next_mil_imps: [], + }, - _each(validBidRequests, (bid) => { - window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; - const id = getPlacementId(bid); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - - const site = getSiteObj(); - const device = getDeviceObj(); - const {cur, mediaTypes} = getCurrency(bid); - - const postBody = { - id: bidderRequest?.bidderRequestId, - cur, - ext: { - prebid: { - storedrequest: { - id, - }, - }, + device, + site, + source, + imp: [], + }; - nextMillennium: { - nm_version: NM_VERSION, - pbjs_version: getGlobal()?.version || undefined, - refresh_count: window.nmmRefreshCounts[bid.adUnitCode]++, - elOffsets: getBoundingClient(bid), - scrollTop: window.pageYOffset || document.documentElement.scrollTop, - }, - }, + setConsentStrings(postBody, bidderRequest); + setOrtb2Parameters(postBody, bidderRequest?.ortb2); - device, - site, - imp: [], - }; + const urlParameters = parseUrl(getWindowTop().location.href).search; + const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; + setEids(postBody, validBidRequests); + _each(validBidRequests, (bid, i) => { + window.nmmRefreshCounts[bid.adUnitCode] = window.nmmRefreshCounts[bid.adUnitCode] || 0; + const id = getPlacementId(bid); + const {cur, mediaTypes} = getCurrency(bid); + if (i === 0) postBody.cur = cur; postBody.imp.push(getImp(bid, id, mediaTypes)); - setConsentStrings(postBody, bidderRequest); - setOrtb2Parameters(postBody, bidderRequest?.ortb2); - setEids(postBody, bid); - - const urlParameters = parseUrl(getWindowTop().location.href).search; - const isTest = urlParameters['pbs'] && urlParameters['pbs'] === 'test'; - const params = bid.params; - - requests.push({ - method: 'POST', - url: isTest ? TEST_ENDPOINT : ENDPOINT, - data: JSON.stringify(postBody), - options: { - contentType: 'text/plain', - withCredentials: true, - }, + postBody.ext.next_mil_imps.push(getExtNextMilImp(bid)); + }); - bidId, - params, - auctionId, - }); + this.getUrlPixelMetric(EVENTS.BID_REQUESTED, validBidRequests); - this.getUrlPixelMetric(EVENTS.BID_REQUESTED, bid); + requests.push({ + method: 'POST', + url: isTest ? TEST_ENDPOINT : ENDPOINT, + data: JSON.stringify(postBody), + options: { + contentType: 'text/plain', + withCredentials: true, + }, }); return requests; }, - interpretResponse: function(serverResponse, bidRequest) { + interpretResponse: function(serverResponse) { const response = serverResponse.body; const bidResponses = []; + const bids = []; _each(response.seatbid, (resp) => { _each(resp.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; + const requestId = bid.impid; const {ad, adUrl, vastUrl, vastXml} = getAd(bid); const bidResponse = { requestId, - params, cpm: bid.price, width: bid.w, height: bid.h, @@ -182,11 +173,13 @@ export const spec = { }; bidResponses.push(bidResponse); - - this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bid); }); + + bids.push(resp.bid); }); + this.getUrlPixelMetric(EVENTS.BID_RESPONSE, bids.flat()); + return bidResponses; }, @@ -226,22 +219,26 @@ export const spec = { triggerPixel(url); }, - _getUrlPixelMetric(eventName, bid) { - const bidder = bid.bidder || bid.bidderCode; + _getUrlPixelMetric(eventName, bids) { + if (!Array.isArray(bids)) bids = [bids]; + + const bidder = bids[0]?.bidder || bids[0]?.bidderCode; if (bidder != BIDDER_CODE) return; - let params; - if (bid.params) { - params = Array.isArray(bid.params) ? bid.params : [bid.params]; - } else { - if (Array.isArray(bid.bids)) params = bid.bids.map(bidI => bidI.params); - }; + let params = []; + _each(bids, bid => { + if (bid.params) { + params.push(bid.params); + } else { + if (Array.isArray(bid.bids)) params.push(bid.bids.map(bidI => bidI.params)); + }; + }); if (!params.length) return; const placementIdsArray = []; const groupIdsArray = []; - params.forEach(paramsI => { + params.flat().forEach(paramsI => { if (paramsI.group_id) { groupIdsArray.push(paramsI.group_id); } else { @@ -252,9 +249,7 @@ export const spec = { const placementIds = (placementIdsArray.length && `&placements=${placementIdsArray.join(';')}`) || ''; const groupIds = (groupIdsArray.length && `&groups=${groupIdsArray.join(';')}`) || ''; - if (!(groupIds || placementIds)) { - return; - }; + if (!(groupIds || placementIds)) return; const url = `${REPORT_ENDPOINT}?event=${eventName}&bidder=${bidder}&source=pbjs${groupIds}${placementIds}`; @@ -268,10 +263,25 @@ export const spec = { }, }; +function getExtNextMilImp(bid) { + if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode]; + const nextMilImp = { + impId: bid.bidId, + nextMillennium: { + nm_version: NM_VERSION, + pbjs_version: PBJS_VERSION, + refresh_count: window?.nmmRefreshCounts[bid.adUnitCode] || 0, + scrollTop: window.pageYOffset || document.documentElement.scrollTop, + }, + }; + + return nextMilImp; +} + export function getImp(bid, id, mediaTypes) { const {banner, video} = mediaTypes; const imp = { - id: bid.adUnitCode, + id: bid.bidId, ext: { prebid: { storedrequest: { @@ -330,10 +340,10 @@ export function setConsentStrings(postBody = {}, bidderRequest) { if (!gppConsent && bidderRequest?.ortb2?.regs?.gpp) gppConsent = bidderRequest?.ortb2?.regs; if (gdprConsent || uspConsent || gppConsent) { - postBody.regs = { ext: {} }; + postBody.regs = {}; if (uspConsent) { - postBody.regs.ext.us_privacy = uspConsent; + postBody.regs.us_privacy = uspConsent; }; if (gppConsent) { @@ -343,15 +353,19 @@ export function setConsentStrings(postBody = {}, bidderRequest) { if (gdprConsent) { if (typeof gdprConsent.gdprApplies !== 'undefined') { - postBody.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + postBody.regs.gdpr = gdprConsent.gdprApplies ? 1 : 0; }; if (typeof gdprConsent.consentString !== 'undefined') { postBody.user = { - ext: { consent: gdprConsent.consentString }, + consent: gdprConsent.consentString, }; }; }; + + if (typeof bidderRequest?.ortb2?.regs?.coppa === 'number') { + postBody.regs.coppa = bidderRequest?.ortb2?.regs?.coppa; + }; }; }; @@ -360,12 +374,20 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) { const value = deepAccess(ortb2, parameter); if (value) deepSetValue(postBody, parameter, value); } + + if (postBody.wlang) delete postBody.wlangb } -export function setEids(postBody, bid) { - if (!isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; +export function setEids(postBody = {}, bids = []) { + let isFind = false; + _each(bids, bid => { + if (isFind || !isArray(bid.userIdAsEids) || !bid.userIdAsEids.length) return; - deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + if (bid.userIdAsEids.length) { + deepSetValue(postBody, 'user.eids', bid.userIdAsEids); + isFind = true; + }; + }); } export function replaceUsersyncMacros(url, gdprConsent = {}, uspConsent = '', gppConsent = {}, type = '') { @@ -397,8 +419,8 @@ function getCurrency(bid = {}) { if (typeof bid.getFloor === 'function') { let floorInfo = bid.getFloor({currency, mediaType, size: '*'}); - mediaTypes[mediaType].bidfloorcur = floorInfo.currency; - mediaTypes[mediaType].bidfloor = floorInfo.floor; + mediaTypes[mediaType].bidfloorcur = floorInfo?.currency; + mediaTypes[mediaType].bidfloor = floorInfo?.floor; } else { mediaTypes[mediaType].bidfloorcur = currency; }; @@ -411,21 +433,7 @@ function getCurrency(bid = {}) { return {cur, mediaTypes}; } -function getAdEl(bid) { - // best way I could think of to get El, is by matching adUnitCode to google slots... - const slot = window.googletag && window.googletag.pubads && window.googletag.pubads().getSlots().find(slot => slot.getAdUnitPath() === bid.adUnitCode); - const slotElementId = slot && slot.getSlotElementId(); - if (!slotElementId) return null; - return document.querySelector('#' + slotElementId); -} - -function getBoundingClient(bid) { - const el = getAdEl(bid); - if (!el) return {}; - return el.getBoundingClientRect(); -} - -function getPlacementId(bid) { +export function getPlacementId(bid) { const groupId = getBidIdParameter('group_id', bid.params); const placementId = getBidIdParameter('placement_id', bid.params); if (!groupId) return placementId; @@ -433,8 +441,8 @@ function getPlacementId(bid) { let windowTop = getTopWindow(window); let sizes = []; if (bid.mediaTypes) { - if (bid.mediaTypes.banner) sizes = bid.mediaTypes.banner.sizes; - if (bid.mediaTypes.video) sizes = [bid.mediaTypes.video.playerSize]; + if (bid.mediaTypes.banner) sizes = [...bid.mediaTypes.banner.sizes]; + if (bid.mediaTypes.video) sizes.push(bid.mediaTypes.video.playerSize); }; const host = (windowTop && windowTop.location && windowTop.location.host) || ''; @@ -455,32 +463,6 @@ function getTopWindow(curWindow, nesting = 0) { }; } -function getAd(bid) { - let ad, adUrl, vastXml, vastUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - case VIDEO: - if (bid.adm.substr(0, 4) === 'http') { - vastUrl = bid.adm; - } else { - vastXml = bid.adm; - }; - - break; - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - }; - }; - - return {ad, adUrl, vastXml, vastUrl}; -} - function getSiteObj() { const refInfo = (getRefererInfo && getRefererInfo()) || {}; @@ -511,6 +493,19 @@ function getDeviceObj() { }; } +export function getSourceObj(validBidRequests, bidderRequest) { + const schain = validBidRequests?.[0]?.schain || + (bidderRequest?.ortb2?.source && (bidderRequest?.ortb2?.source?.schain || bidderRequest?.ortb2?.source?.ext?.schain)); + + if (!schain) return; + + const source = { + schain, + }; + + return source; +} + function getSua() { let {brands, mobile, platform} = (window?.navigator?.userAgentData || {}); if (!(brands && platform)) return undefined; diff --git a/modules/nextrollBidAdapter.js b/modules/nextrollBidAdapter.js index 8a41efe4dcc..689f2120de2 100644 --- a/modules/nextrollBidAdapter.js +++ b/modules/nextrollBidAdapter.js @@ -11,6 +11,7 @@ import { import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { getOsVersion } from '../libraries/advangUtils/index.js'; import {find} from '../src/polyfill.js'; @@ -312,7 +313,7 @@ function _getDevice(_bidRequest) { ua: navigator.userAgent, language: navigator['language'], os: _getOs(navigator.userAgent.toLowerCase()), - osv: _getOsVersion(navigator.userAgent) + osv: getOsVersion() }; } @@ -343,25 +344,4 @@ function _getOs(userAgent) { }) || '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/nexx360BidAdapter.js b/modules/nexx360BidAdapter.js index c31c3d81aeb..9a76b24f223 100644 --- a/modules/nexx360BidAdapter.js +++ b/modules/nexx360BidAdapter.js @@ -22,7 +22,7 @@ const OUTSTREAM_RENDERER_URL = 'https://acdn.adnxs.com/video/outstream/ANOutstre const BIDDER_CODE = 'nexx360'; const REQUEST_URL = 'https://fast.nexx360.io/booster'; const PAGE_VIEW_ID = generateUUID(); -const BIDDER_VERSION = '4.0'; +const BIDDER_VERSION = '4.2'; const GVLID = 965; const NEXXID_KEY = 'nexx360_storage'; @@ -33,6 +33,8 @@ const ALIASES = [ { code: 'league-m', gvlid: 965 }, { code: 'prjads' }, { code: 'pubtech' }, + { code: '1accord', gvlid: 965 }, + { code: 'easybid', gvlid: 1068 }, ]; export const storage = getStorageManager({ @@ -69,6 +71,19 @@ function getAdContainer(container) { } } +/** + * Get the AMX ID + * @return {string | false } false if localstorageNotEnabled + */ +export function getAmxId() { + if (!storage.localStorageIsEnabled()) { + logInfo(`localstorage not enabled for Nexx360`); + return false; + } + const amxId = storage.getDataFromLocalStorage('__amuidpb'); + return amxId || false; +} + const converter = ortbConverter({ context: { netRevenue: true, // or false if your adapter should set bidResponse.netRevenue = false @@ -107,13 +122,30 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); const nexx360LocalStorage = getNexx360LocalStorage(); - if (nexx360LocalStorage) deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + if (nexx360LocalStorage) { + deepSetValue(request, 'ext.nexx360Id', nexx360LocalStorage.nexx360Id); + deepSetValue(request, 'ext.localStorage.nexx360Id', nexx360LocalStorage.nexx360Id); + } + const amxId = getAmxId(); + if (amxId) deepSetValue(request, 'ext.localStorage.amxId', amxId); deepSetValue(request, 'ext.version', '$prebid.version$'); deepSetValue(request, 'ext.source', 'prebid.js'); deepSetValue(request, 'ext.pageViewId', PAGE_VIEW_ID); deepSetValue(request, 'ext.bidderVersion', BIDDER_VERSION); deepSetValue(request, 'cur', [config.getConfig('currency.adServerCurrency') || 'USD']); - if (!request.user) deepSetValue(request, 'user', {}); + if (!request.user) request.user = {}; + if (getAmxId()) { + if (!request.user.ext) request.user.ext = {}; + if (!request.user.ext.eids) request.user.ext.eids = []; + request.user.ext.eids.push({ + source: 'amxdt.net', + uids: [{ + id: `${getAmxId()}`, + atype: 1 + }] + }); + } + return request; }, }); diff --git a/modules/nexx360BidAdapter.md b/modules/nexx360BidAdapter.md index 532d48418b6..5935b568a13 100644 --- a/modules/nexx360BidAdapter.md +++ b/modules/nexx360BidAdapter.md @@ -30,8 +30,7 @@ var adUnits = [ bids: [{ bidder: 'nexx360', params: { - account: '1067', - tagId: 'luvxjvgn' + tagId: 'testnexx' } }] }, @@ -51,8 +50,7 @@ var adUnits = [ bids: [{ bidder: 'nexx360', params: { - account: '1067', - tagId: 'luvxjvgn' + tagId: 'testnexx' } }] }; diff --git a/modules/nobidAnalyticsAdapter.js b/modules/nobidAnalyticsAdapter.js index a0aa5ee4989..afa980b05c9 100644 --- a/modules/nobidAnalyticsAdapter.js +++ b/modules/nobidAnalyticsAdapter.js @@ -1,4 +1,4 @@ -import {deepClone, logError, getParameterByName} from '../src/utils.js'; +import {deepClone, logError, getParameterByName, logMessage} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {getStorageManager} from '../src/storageManager.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -26,8 +26,7 @@ const { AD_RENDER_SUCCEEDED } = EVENTS; function log (msg) { - // eslint-disable-next-line no-console - console.log(`%cNoBid Analytics ${VERSION}`, 'padding: 2px 8px 2px 8px; background-color:#f50057; color: white', msg); + logMessage(`%cNoBid Analytics ${VERSION}: ${msg}`); } function isJson (str) { return str && str.startsWith('{') && str.endsWith('}'); @@ -241,7 +240,7 @@ window.nobidCarbonizer = { adunit.bids = allowedBidders; } for (const adunit of adunits) { - if (!nobidAnalytics.originalAdUnits[adunit.code]) nobidAnalytics.originalAdUnits[adunit.code] = JSON.parse(JSON.stringify(adunit)); + if (!nobidAnalytics.originalAdUnits[adunit.code]) nobidAnalytics.originalAdUnits[adunit.code] = deepClone(adunit); }; if (this.isActive()) { // 5% of the time do not block; diff --git a/modules/nobidBidAdapter.js b/modules/nobidBidAdapter.js index 77e5a1c6fed..7cacac819c1 100644 --- a/modules/nobidBidAdapter.js +++ b/modules/nobidBidAdapter.js @@ -3,7 +3,7 @@ import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -231,7 +231,7 @@ function nobidBuildRequests(bids, bidderRequest) { return adunits; } function getFloor (bid) { - if (bid && typeof bid.getFloor === 'function' && bid.getFloor().floor) { + if (bid && typeof bid.getFloor === 'function' && bid.getFloor()?.floor) { return bid.getFloor().floor; } return null; diff --git a/modules/novatiqIdSystem.md b/modules/novatiqIdSystem.md index f33fc700311..a78363e8fe3 100644 --- a/modules/novatiqIdSystem.md +++ b/modules/novatiqIdSystem.md @@ -19,12 +19,11 @@ pbjs.setConfig({ name: 'novatiq', params: { // change to the Partner Number you received from Novatiq - sourceid '1a3' - } + sourceid: '1a3' } }], // 50ms maximum auction delay, applies to all userId modules - auctionDelay: 50 + auctionDelay: 50 } }); ``` diff --git a/modules/oguryBidAdapter.js b/modules/oguryBidAdapter.js index 3cf78da4e3a..ecf512f11d3 100644 --- a/modules/oguryBidAdapter.js +++ b/modules/oguryBidAdapter.js @@ -1,7 +1,7 @@ 'use strict'; import {BANNER} from '../src/mediaTypes.js'; -import {getWindowSelf, getWindowTop, isFn, logWarn} from '../src/utils.js'; +import {getWindowSelf, getWindowTop, isFn, logWarn, deepAccess, isPlainObject} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {ajax} from '../src/ajax.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; @@ -12,7 +12,7 @@ const DEFAULT_TIMEOUT = 1000; const BID_HOST = 'https://mweb-hb.presage.io/api/header-bidding-request'; const TIMEOUT_MONITORING_HOST = 'https://ms-ads-monitoring-events.presage.io'; const MS_COOKIE_SYNC_DOMAIN = 'https://ms-cookie-sync.presage.io'; -const ADAPTER_VERSION = '1.6.0'; +const ADAPTER_VERSION = '1.7.0'; function getClientWidth() { const documentElementClientWidth = window.top.document.documentElement.clientWidth @@ -46,14 +46,16 @@ function isBidRequestValid(bid) { return (isValidSizes && isValidAdUnitId && isValidAssetKey); } -function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { const consent = (gdprConsent && gdprConsent.consentString) || ''; + const gpp = (gppConsent && gppConsent.gppString) || ''; + const gppSid = (gppConsent && gppConsent.applicableSections && gppConsent.applicableSections.toString()) || ''; if (syncOptions.iframeEnabled) { return [ { type: 'iframe', - url: `${MS_COOKIE_SYNC_DOMAIN}/user-sync.html?gdpr_consent=${consent}&source=prebid` + url: `${MS_COOKIE_SYNC_DOMAIN}/user-sync.html?gdpr_consent=${consent}&source=prebid&gpp=${gpp}&gpp_sid=${gppSid}` } ]; } @@ -62,15 +64,15 @@ function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent) { return [ { type: 'image', - url: `${MS_COOKIE_SYNC_DOMAIN}/v1/init-sync/bid-switch?iab_string=${consent}&source=prebid` + url: `${MS_COOKIE_SYNC_DOMAIN}/v1/init-sync/bid-switch?iab_string=${consent}&source=prebid&gpp=${gpp}&gpp_sid=${gppSid}` }, { type: 'image', - url: `${MS_COOKIE_SYNC_DOMAIN}/ttd/init-sync?iab_string=${consent}&source=prebid` + url: `${MS_COOKIE_SYNC_DOMAIN}/ttd/init-sync?iab_string=${consent}&source=prebid&gpp=${gpp}&gpp_sid=${gppSid}` }, { type: 'image', - url: `${MS_COOKIE_SYNC_DOMAIN}/xandr/init-sync?iab_string=${consent}&source=prebid` + url: `${MS_COOKIE_SYNC_DOMAIN}/xandr/init-sync?iab_string=${consent}&source=prebid&gpp=${gpp}&gpp_sid=${gppSid}` } ]; } @@ -85,7 +87,7 @@ function buildRequests(validBidRequests, bidderRequest) { at: 1, regs: { ext: { - gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0 + gdpr: bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies ? 1 : 0, }, }, site: { @@ -112,6 +114,12 @@ function buildRequests(validBidRequests, bidderRequest) { if (bidderRequest.gdprConsent && bidderRequest.gdprConsent.consentString) { openRtbBidRequestBanner.user.ext.consent = bidderRequest.gdprConsent.consentString } + if (bidderRequest.gppConsent && bidderRequest.gppConsent.gppString) { + openRtbBidRequestBanner.regs.ext.gpp = bidderRequest.gppConsent.gppString + } + if (bidderRequest.gppConsent && bidderRequest.gppConsent.applicableSections) { + openRtbBidRequestBanner.regs.ext.gpp_sid = bidderRequest.gppConsent.applicableSections + } validBidRequests.forEach((bidRequest) => { const sizes = getAdUnitSizes(bidRequest) @@ -129,6 +137,8 @@ function buildRequests(validBidRequests, bidderRequest) { openRtbBidRequestBanner.user.ext.eids = bidRequest.userIdAsEids } + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid'); + openRtbBidRequestBanner.imp.push({ id: bidRequest.bidId, tagid: bidRequest.params.adUnitId, @@ -138,6 +148,7 @@ function buildRequests(validBidRequests, bidderRequest) { }, ext: { ...bidRequest.params, + ...(gpid && {gpid}), timeSpentOnPage: document.timeline && document.timeline.currentTime ? document.timeline.currentTime : 0 } }); @@ -200,7 +211,7 @@ function getFloor(bid) { mediaType: 'banner', size: '*' }); - return floorResult.currency === 'USD' ? floorResult.floor : 0; + return (isPlainObject(floorResult) && floorResult.currency === 'USD') ? floorResult.floor : 0; } function getWindowContext() { diff --git a/modules/omsBidAdapter.js b/modules/omsBidAdapter.js index e6c8f8b098e..901b9a138d5 100644 --- a/modules/omsBidAdapter.js +++ b/modules/omsBidAdapter.js @@ -10,15 +10,18 @@ import { isPlainObject, getBidIdParameter, getUniqueIdentifierStr, + formatQS, } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {ajax} from '../src/ajax.js'; import {percentInView} from '../libraries/percentInView/percentInView.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; const BIDDER_CODE = 'oms'; const URL = 'https://rt.marphezis.com/hb'; -const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid' +const TRACK_EVENT_URL = 'https://rt.marphezis.com/prebid'; +const USER_SYNC_URL_IFRAME = 'https://rt.marphezis.com/sync?dpid=0'; export const spec = { code: BIDDER_CODE, @@ -136,7 +139,7 @@ function buildRequests(bidReqs, bidderRequest) { } function isBidRequestValid(bid) { - if (bid.bidder !== BIDDER_CODE || !bid.params || !bid.params.publisherId) { + if (!bid.params || !bid.params.publisherId) { return false; } @@ -179,9 +182,20 @@ function interpretResponse(serverResponse) { return response; } -// Don't do user sync for now -function getUserSyncs(syncOptions, responses, gdprConsent) { - return []; +function getUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) { + const syncs = []; + + if (syncOptions.iframeEnabled) { + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); + params = Object.keys(params).length ? `&${formatQS(params)}` : ''; + + syncs.push({ + type: 'iframe', + url: USER_SYNC_URL_IFRAME + params, + }); + } + + return syncs; } function onBidderError(errorData) { diff --git a/modules/omsBidAdapter.md b/modules/omsBidAdapter.md index f1e2d459eca..0a6b9cac82c 100644 --- a/modules/omsBidAdapter.md +++ b/modules/omsBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: OMS Bid Adapter Module Type: Bidder Adapter -Maintainer: alexandruc@onlinemediasolutions.com +Maintainer: devsupport@onlinemediasolutions.com ``` # Description diff --git a/modules/onetagBidAdapter.js b/modules/onetagBidAdapter.js index d8423253aaf..8bf326b4848 100644 --- a/modules/onetagBidAdapter.js +++ b/modules/onetagBidAdapter.js @@ -93,7 +93,7 @@ function buildRequests(validBidRequests, bidderRequest) { const connection = navigator.connection || navigator.webkitConnection; payload.networkConnectionType = (connection && connection.type) ? connection.type : null; payload.networkEffectiveConnectionType = (connection && connection.effectiveType) ? connection.effectiveType : null; - payload.fledgeEnabled = Boolean(bidderRequest && bidderRequest.fledgeEnabled) + payload.fledgeEnabled = Boolean(bidderRequest?.paapi?.enabled) return { method: 'POST', url: ENDPOINT, @@ -156,7 +156,7 @@ function interpretResponse(serverResponse, bidderRequest) { const fledgeAuctionConfigs = body.fledgeAuctionConfigs return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return bids; @@ -291,6 +291,7 @@ function setGeneralInfo(bidRequest) { this['gpid'] = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); this['pubId'] = params.pubId; this['ext'] = params.ext; + this['ortb2Imp'] = deepAccess(bidRequest, 'ortb2Imp'); if (params.pubClick) { this['click'] = params.pubClick; } @@ -410,7 +411,7 @@ function getBidFloor(bidRequest, mediaType, sizes) { currency: 'EUR', mediaType: mediaType || '*', size: [size.width, size.height] - }); + }) || {}; floor.size = deepClone(size); if (!floor.floor) { floor.floor = null; } priceFloors.push(floor); diff --git a/modules/ooloAnalyticsAdapter.js b/modules/ooloAnalyticsAdapter.js index 8a6ef88a7fb..573fee3b0b3 100644 --- a/modules/ooloAnalyticsAdapter.js +++ b/modules/ooloAnalyticsAdapter.js @@ -433,6 +433,11 @@ function sendPage() { function sendHbConfigData() { const conf = {} const pbjsConfig = config.getConfig() + // Check if pbjsConfig.userSync exists and has userIds property + if (pbjsConfig.userSync && pbjsConfig.userSync.userIds) { + // Delete the userIds property + delete pbjsConfig.userSync.userIds; + } Object.keys(pbjsConfig).forEach(key => { if (key[0] !== '_') { diff --git a/modules/openwebBidAdapter.js b/modules/openwebBidAdapter.js index cf0334b7f29..60364f41d3c 100644 --- a/modules/openwebBidAdapter.js +++ b/modules/openwebBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - triggerPixel, - isInteger, - getBidIdParameter, - getDNT + triggerPixel } 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 { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'openweb'; const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.openwebmp.com/'; +const BASE_URL = 'https://hb.openwebmp.com/'; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -46,6 +41,11 @@ export const spec = { return false; } + if (!bidRequest.params.placementId) { + logWarn('placementId is a mandatory param for OpenWeb adapter'); + return false; + } + return true; }, buildRequests: function (validBidRequests, bidderRequest) { @@ -60,7 +60,7 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject } }, @@ -69,32 +69,7 @@ export const spec = { if (body && body.bids && body.bids.length) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -104,20 +79,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -135,351 +110,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(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 !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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && 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'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`); - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = deepAccess(bidderRequest, 'refererInfo.domain') || window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode, timeout} = bidderRequest; - const generalBidParams = generalObject.params; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: getDNT() ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.auctionStart) { - generalParams.auction_start = bidderRequest.auctionStart; - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/openxBidAdapter.js b/modules/openxBidAdapter.js index a99bd1c5325..19da19e661f 100644 --- a/modules/openxBidAdapter.js +++ b/modules/openxBidAdapter.js @@ -4,7 +4,6 @@ import * as utils from '../src/utils.js'; import {mergeDeep} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const bidderConfig = 'hb_pb_ortb'; const bidderVersion = '2.0'; @@ -18,8 +17,7 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - getUserSyncs, - transformBidParams + getUserSyncs }; registerBidder(spec); @@ -82,11 +80,6 @@ const converter = ortbConverter({ bidResponse.meta.advertiserId = bid.ext.buyer_id; bidResponse.meta.brandId = bid.ext.brand_id; } - const {ortbResponse} = context; - if (ortbResponse.ext && ortbResponse.ext.paf) { - bidResponse.meta.paf = Object.assign({}, ortbResponse.ext.paf); - bidResponse.meta.paf.content_id = utils.deepAccess(bid, 'ext.paf.content_id'); - } return bidResponse; }, response(buildResponse, bidResponses, ortbResponse, context) { @@ -116,10 +109,10 @@ const converter = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { - return response.bids + return response } }, overrides: { @@ -144,22 +137,12 @@ const converter = ortbConverter({ bidRequest = {...bidRequest, mediaTypes: {[VIDEO]: videoParams}} } orig(imp, bidRequest, context); - if (imp.video && videoParams?.context === 'outstream') { - imp.video.placement = imp.video.placement || 4; - } } } } } }); -function transformBidParams(params, isOpenRtb) { - return convertTypes({ - 'unit': 'string', - 'customFloor': 'number' - }, params); -} - function isBidRequestValid(bidRequest) { const hasDelDomainOrPlatform = bidRequest.params.delDomain || bidRequest.params.platform; diff --git a/modules/operaadsBidAdapter.js b/modules/operaadsBidAdapter.js index 957192d1bec..486d5ac726b 100644 --- a/modules/operaadsBidAdapter.js +++ b/modules/operaadsBidAdapter.js @@ -525,7 +525,6 @@ function createImp(bidRequest) { 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; diff --git a/modules/opscoBidAdapter.js b/modules/opscoBidAdapter.js index 87d00f14de0..2ad14227804 100644 --- a/modules/opscoBidAdapter.js +++ b/modules/opscoBidAdapter.js @@ -19,7 +19,11 @@ export const spec = { Array.isArray(bid.mediaTypes?.banner?.sizes)), buildRequests: (validBidRequests, bidderRequest) => { - const {publisherId, placementId, siteId} = validBidRequests[0].params; + if (!validBidRequests || !bidderRequest) { + return; + } + + const {publisherId, siteId} = validBidRequests[0].params; const payload = { id: bidderRequest.bidderRequestId, @@ -28,7 +32,7 @@ export const spec = { banner: {format: extractSizes(bidRequest)}, ext: { opsco: { - placementId: placementId, + placementId: bidRequest.params.placementId, publisherId: publisherId, } } diff --git a/modules/optableBidAdapter.js b/modules/optableBidAdapter.js index f6c7cf00a35..d2dae252e6c 100644 --- a/modules/optableBidAdapter.js +++ b/modules/optableBidAdapter.js @@ -17,17 +17,42 @@ const BIDDER_CODE = 'optable'; const DEFAULT_REGION = 'ca' const DEFAULT_ORIGIN = 'https://ads.optable.co' +function getOrigin() { + return config.getConfig('optable.origin') ?? DEFAULT_ORIGIN; +} + +function getBaseUrl() { + const region = config.getConfig('optable.region') ?? DEFAULT_REGION; + return `${getOrigin()}/${region}` +} + export const spec = { code: BIDDER_CODE, isBidRequestValid: function(bid) { return !!bid.params?.site }, buildRequests: function(bidRequests, bidderRequest) { - const region = config.getConfig('optable.region') ?? DEFAULT_REGION - const origin = config.getConfig('optable.origin') ?? DEFAULT_ORIGIN - const requestURL = `${origin}/${region}/ortb2/v1/ssp/bid` + const requestURL = `${getBaseUrl()}/ortb2/v1/ssp/bid` const data = converter.toORTB({ bidRequests, bidderRequest, context: { mediaType: BANNER } }); - return { method: 'POST', url: requestURL, data } }, + buildPAAPIConfigs: function(bidRequests) { + const origin = getOrigin(); + return bidRequests + .filter(req => req.ortb2Imp?.ext?.ae) + .map(bid => ({ + bidId: bid.bidId, + config: { + seller: origin, + decisionLogicURL: `${getBaseUrl()}/paapi/v1/ssp/decision-logic.js?origin=${bid.params.site}`, + interestGroupBuyers: [origin], + perBuyerMultiBidLimits: { + [origin]: 100 + }, + perBuyerCurrencies: { + [origin]: 'USD' + } + } + })) + }, interpretResponse: function(response, request) { const bids = converter.fromORTB({ response: response.body, request: request.data }).bids const auctionConfigs = (response.body.ext?.optable?.fledge?.auctionconfigs ?? []).map((cfg) => { @@ -35,7 +60,7 @@ export const spec = { return { bidId: impid, config } }) - return { bids, fledgeAuctionConfigs: auctionConfigs } + return { bids, paapi: auctionConfigs } }, supportedMediaTypes: [BANNER] } diff --git a/modules/optidigitalBidAdapter.js b/modules/optidigitalBidAdapter.js index 396131fd8aa..f8dea0f5a92 100755 --- a/modules/optidigitalBidAdapter.js +++ b/modules/optidigitalBidAdapter.js @@ -1,6 +1,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; -import {deepAccess, parseSizesInput} from '../src/utils.js'; +import {deepAccess, isPlainObject, parseSizesInput} from '../src/utils.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; /** @@ -247,7 +247,7 @@ function _getFloor (bid, sizes, currency) { mediaType: 'banner', size: size }); - if (typeof floorInfo === 'object' && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === CUR && !isNaN(parseFloat(floorInfo.floor))) { floor = parseFloat(floorInfo.floor); } } catch (err) {} diff --git a/modules/optimeraRtdProvider.js b/modules/optimeraRtdProvider.js index bd564e3a260..71cd2a3b79b 100644 --- a/modules/optimeraRtdProvider.js +++ b/modules/optimeraRtdProvider.js @@ -187,7 +187,11 @@ export function setScoresURL() { if (apiVersion === 'v1') { newScoresURL = `${baseUrl}api/products/scores?c=${clientID}&h=${optimeraHost}&p=${optimeraPathName}&s=${device}`; } else { - newScoresURL = `${baseUrl}${clientID}/${optimeraHost}${optimeraPathName}.js`; + let encoded = encodeURIComponent(`${optimeraHost}${optimeraPathName}`) + .replaceAll('%2F', '/') + .replaceAll('%20', '+'); + + newScoresURL = `${baseUrl}${clientID}/${encoded}.js`; } if (scoresURL !== newScoresURL) { diff --git a/modules/optoutBidAdapter.js b/modules/optoutBidAdapter.js index f7b5934665c..f0010d54833 100644 --- a/modules/optoutBidAdapter.js +++ b/modules/optoutBidAdapter.js @@ -1,7 +1,7 @@ import { deepAccess } from '../src/utils.js'; import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; const BIDDER_CODE = 'optout'; diff --git a/modules/orakiBidAdapter.js b/modules/orakiBidAdapter.js new file mode 100644 index 00000000000..d3ef143249b --- /dev/null +++ b/modules/orakiBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'oraki'; +const AD_URL = 'https://eu1.oraki.io/pbjs'; +const SYNC_URL = 'https://sync.oraki.io'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/orakiBidAdapter.md b/modules/orakiBidAdapter.md new file mode 100644 index 00000000000..ce35021a937 --- /dev/null +++ b/modules/orakiBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Oraki Bidder Adapter +Module Type: Oraki Bidder Adapter +Maintainer: ben@oraki.io +``` + +# Description + +Connects to Oraki exchange for bids. +Oraki 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: 'oraki', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'oraki', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'oraki', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/orbidderBidAdapter.js b/modules/orbidderBidAdapter.js index 0f912384db7..dc8293e74f2 100644 --- a/modules/orbidderBidAdapter.js +++ b/modules/orbidderBidAdapter.js @@ -3,7 +3,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { getStorageManager } from '../src/storageManager.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -93,6 +92,9 @@ export const spec = { if (bidderRequest && bidderRequest.refererInfo) { referer = bidderRequest.refererInfo.page || ''; } + if (bidRequest?.mediaTypes?.video) { + delete bidRequest.mediaTypes.video; + } bidRequest.params.bidfloor = getBidFloor(bidRequest); @@ -101,7 +103,7 @@ export const spec = { method: 'POST', options: { withCredentials: true }, data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', pageUrl: referer, ...bidRequest // get all data provided by bid request } diff --git a/modules/outbrainBidAdapter.js b/modules/outbrainBidAdapter.js index 6015ff37e08..f626b2485d1 100644 --- a/modules/outbrainBidAdapter.js +++ b/modules/outbrainBidAdapter.js @@ -5,7 +5,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import { getStorageManager } from '../src/storageManager.js'; import {OUTSTREAM} from '../src/video.js'; -import {_map, deepAccess, deepSetValue, isArray, logWarn, replaceAuctionPrice} from '../src/utils.js'; +import {_map, deepAccess, deepSetValue, logWarn, replaceAuctionPrice, setOnAny, parseGPTSingleSizeArrayToRtbSize, isPlainObject} from '../src/utils.js'; import {ajax} from '../src/ajax.js'; import {config} from '../src/config.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; @@ -94,7 +94,7 @@ export const spec = { imp.video = getVideoAsset(bid); } else { imp.banner = { - format: transformSizes(bid.sizes) + format: bid.sizes?.map((size) => parseGPTSingleSizeArrayToRtbSize(size)) } } @@ -111,7 +111,7 @@ export const spec = { const request = { id: bidderRequest.bidderRequestId, site: { page, publisher }, - device: { ua }, + device: ortb2?.device || { ua }, source: { fd: 1 }, cur: [cur], tmax: timeout, @@ -174,7 +174,7 @@ export const spec = { } const { seatbid, cur } = serverResponse.body; - const bidResponses = flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { + const bidResponses = seatbid.map(seat => seat.bid).flat().reduce((result, bid) => { result[bid.impid - 1] = bid; return result; }, []); @@ -288,19 +288,6 @@ function parseNative(bid) { return result; } -function setOnAny(collection, key) { - for (let i = 0, result; i < collection.length; i++) { - result = deepAccess(collection[i], key); - if (result) { - return result; - } - } -} - -function flatten(arr) { - return [].concat(...arr); -} - function getNativeAssets(bid) { return _map(bid.nativeParams, (bidParams, key) => { const props = NATIVE_PARAMS[key]; @@ -319,7 +306,7 @@ function getNativeAssets(bid) { } if (bidParams.sizes) { - const sizes = flatten(bidParams.sizes); + const sizes = bidParams.sizes.flat(); w = parseInt(sizes[0], 10); h = parseInt(sizes[1], 10); } @@ -339,7 +326,7 @@ function getNativeAssets(bid) { } function getVideoAsset(bid) { - const sizes = flatten(bid.mediaTypes.video.playerSize); + const sizes = bid.mediaTypes.video.playerSize.flat(); return { w: parseInt(sizes[0], 10), h: parseInt(sizes[1], 10), @@ -355,40 +342,18 @@ function getVideoAsset(bid) { maxduration: bid.mediaTypes.video.maxduration, startdelay: bid.mediaTypes.video.startdelay, placement: bid.mediaTypes.video.placement, + plcmt: bid.mediaTypes.video.plcmt, linearity: bid.mediaTypes.video.linearity }; } -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - if (!isArray(requestSizes)) { - return []; - } - - if (requestSizes.length === 2 && !isArray(requestSizes[0])) { - return [{ - w: parseInt(requestSizes[0], 10), - h: parseInt(requestSizes[1], 10) - }]; - } else if (isArray(requestSizes[0])) { - return requestSizes.map(item => - ({ - w: parseInt(item[0], 10), - h: parseInt(item[1], 10) - }) - ); - } - - return []; -} - function _getFloor(bid, type) { const floorInfo = bid.getFloor({ currency: CURRENCY, mediaType: type, size: '*' }); - if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { return parseFloat(floorInfo.floor); } return null; diff --git a/modules/ownadxBidAdapter.js b/modules/ownadxBidAdapter.js new file mode 100644 index 00000000000..5843735f314 --- /dev/null +++ b/modules/ownadxBidAdapter.js @@ -0,0 +1,99 @@ +import { parseSizesInput, isEmpty } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js' + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'ownadx'; +const CUR = 'USD'; +const CREATIVE_TTL = 300; + +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.tokenId && bid.params.sspId && bid.params.seatId); + }, + + /** + * 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 = parseSizesInput(bidRequest.params.size || bidRequest.sizes); + let mtype = 0; + if (bidRequest.mediaTypes[BANNER]) { + mtype = 1; + } else { + mtype = 2; + } + + let tkn = bidRequest.params.tokenId; + let seatid = bidRequest.params.seatId; + let sspid = bidRequest.params.sspId; + + const payload = { + sizes: sizes, + slotBidId: bidRequest.bidId, + PageUrl: bidderRequest.refererInfo.page, + mediaChannel: mtype + }; + return { + method: 'POST', + url: `https://pbs-js.prebid-ownadx.com/publisher/prebid/${seatid}/${sspid}?token=${tkn}`, + data: payload + }; + }); + }, + + /** + * Unpack the response from the server into a list of bids. + * + * @param {ServerResponse} serverResponse A successful response from the server. + * @return {Bid[]} An array of bids which were nested inside the server. + */ + interpretResponse: function (serverResponse) { + const response = serverResponse.body; + const bids = []; + if (isEmpty(response)) { + return bids; + } + const responseBid = { + width: response.width, + height: response.height, + token: response.tokenId, + ttl: CREATIVE_TTL, + requestId: response.slotBidId, + aType: response.adType || '1', + cpm: response.cpm, + creativeId: response.creativeId || 0, + netRevenue: response.netRevenue || false, + currency: response.currency || CUR, + meta: { + mediaType: response.mediaType || BANNER, + advertiserDomains: response.advertiserDomains || [] + }, + ad: response.adm + }; + bids.push(responseBid); + return bids; + } + +}; + +registerBidder(spec); diff --git a/modules/oxxionAnalyticsAdapter.js b/modules/oxxionAnalyticsAdapter.js index b3bc2d3479f..9e18c92d25b 100644 --- a/modules/oxxionAnalyticsAdapter.js +++ b/modules/oxxionAnalyticsAdapter.js @@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; import { ajax } from '../src/ajax.js'; import { getRefererInfo } from '../src/refererDetection.js'; +import { deepClone } from '../src/utils.js'; const analyticsType = 'endpoint'; const url = 'URL_TO_SERVER_ENDPOINT'; @@ -125,7 +126,7 @@ function addTimeout(args) { let stringArgs = JSON.parse(dereferenceWithoutRenderer(args)); argsDereferenced = stringArgs; argsDereferenced.forEach((attr) => { - argsCleaned.push(filterAttributes(JSON.parse(JSON.stringify(attr)), false)); + argsCleaned.push(filterAttributes(deepClone(attr), false)); }); if (auctionEnd[eventType] == undefined) { auctionEnd[eventType] = [] } auctionEnd[eventType].push(argsCleaned); diff --git a/modules/ozoneBidAdapter.js b/modules/ozoneBidAdapter.js index 0d921f57cda..d554c0afeda 100644 --- a/modules/ozoneBidAdapter.js +++ b/modules/ozoneBidAdapter.js @@ -8,7 +8,7 @@ import { contains, mergeDeep, parseUrl, - generateUUID + generateUUID, isInteger, deepClone } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; @@ -22,7 +22,7 @@ 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.9.1'; +const OZONEVERSION = '2.9.4'; export const spec = { gvlid: 524, aliases: [{code: 'lmc', gvlid: 524}, {code: 'venatus', gvlid: 524}], @@ -38,7 +38,7 @@ export const spec = { 'auctionUrl': ORIGIN + AUCTIONURI, 'cookieSyncUrl': ORIGIN + OZONECOOKIESYNC, 'rendererUrl': OZONE_RENDERER_URL, - 'batchRequests': false /* you can change this to true OR override it in the config: config.ozone.batchRequests */ + 'batchRequests': false /* you can change this to true OR numeric OR override it in the config: config.ozone.batchRequests = true/false/number */ }, loadWhitelabelData(bid) { if (this.propertyBag.whitelabel) { return; } @@ -47,7 +47,7 @@ export const spec = { this.propertyBag.whitelabel.logId = bidder.toUpperCase(); this.propertyBag.whitelabel.bidder = bidder; let bidderConfig = config.getConfig(bidder) || {}; - logInfo('got bidderConfig: ', JSON.parse(JSON.stringify(bidderConfig))); + logInfo('got bidderConfig: ', deepClone(bidderConfig)); if (bidderConfig.kvpPrefix) { this.propertyBag.whitelabel.keyPrefix = bidderConfig.kvpPrefix; } @@ -76,10 +76,22 @@ export const spec = { } } if (bidderConfig.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + if (this.batchValueIsValid(bidderConfig.batchRequests)) { + this.propertyBag.whitelabel.batchRequests = bidderConfig.batchRequests; + } else { + logError('bidderConfig.batchRequests must be boolean or a number. Found & ignored data type: ' + typeof bidderConfig.batchRequests); + } + } + if (bidderConfig.hasOwnProperty('videoParams')) { + this.propertyBag.whitelabel.videoParams = bidderConfig.videoParams; } if (arr.hasOwnProperty('batchRequests')) { - this.propertyBag.whitelabel.batchRequests = true; + let getBatch = parseInt(arr.batchRequests); + if (this.batchValueIsValid(getBatch)) { + this.propertyBag.whitelabel.batchRequests = getBatch; + } else { + logError('Ignoring query param: batchRequests - this must be a positive number'); + } } try { if (arr.hasOwnProperty('auction') && arr.auction === 'dev') { @@ -93,6 +105,9 @@ export const spec = { } catch (e) {} logInfo('set propertyBag.whitelabel to', this.propertyBag.whitelabel); }, + batchValueIsValid(batch) { + return typeof batch === 'boolean' || (typeof batch === 'number' && batch > 0); + }, getAuctionUrl() { return this.propertyBag.whitelabel.auctionUrl; }, @@ -102,9 +117,17 @@ export const spec = { getRendererUrl() { return this.propertyBag.whitelabel.rendererUrl; }, - isBatchRequests() { - logInfo('isBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); - return this.propertyBag.whitelabel.batchRequests; + getVideoPlacementValue: function(context) { + if (['instream', 'outstream'].indexOf(context) < 0) return null; + return deepAccess(this.propertyBag, `whitelabel.videoParams.${context}`, null); + }, + getBatchRequests() { + logInfo('getBatchRequests going to return ', this.propertyBag.whitelabel.batchRequests); + if (this.propertyBag.whitelabel.batchRequests === true) { return 10; } + if (typeof this.propertyBag.whitelabel.batchRequests === 'number' && this.propertyBag.whitelabel.batchRequests > 0) { + return this.propertyBag.whitelabel.batchRequests; + } + return false; }, isBidRequestValid(bid) { this.loadWhitelabelData(bid); @@ -177,13 +200,14 @@ export const spec = { this.propertyBag.buildRequestsStart = new Date().getTime(); let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; - logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, JSON.parse(JSON.stringify(validBidRequests)), 'bidderRequest', JSON.parse(JSON.stringify(bidderRequest))); + logInfo(`buildRequests time: ${this.propertyBag.buildRequestsStart} v ${OZONEVERSION} validBidRequests`, deepClone(validBidRequests), 'bidderRequest', deepClone(bidderRequest)); if (this.blockTheRequest()) { return []; } + let fledgeEnabled = !!bidderRequest.fledgeEnabled; // IF true then this is added as each bid[].ext.ae=1 let htmlParams = {'publisherId': '', 'siteId': ''}; if (validBidRequests.length > 0) { - this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIds(validBidRequests[0])); + this.cookieSyncBag.userIdObject = Object.assign(this.cookieSyncBag.userIdObject, this.findAllUserIdsFromEids(validBidRequests[0])); this.cookieSyncBag.siteId = deepAccess(validBidRequests[0], 'params.siteId'); this.cookieSyncBag.publisherId = deepAccess(validBidRequests[0], 'params.publisherId'); htmlParams = validBidRequests[0].params; @@ -203,9 +227,13 @@ export const spec = { const getParams = this.getGetParametersAsObject(); const wlOztestmodeKey = whitelabelPrefix + 'testmode'; const isTestMode = getParams[wlOztestmodeKey] || null; // this can be any string, it's used for testing ads - ozoneRequest.device = {'w': window.innerWidth, 'h': window.innerHeight}; + ozoneRequest.device = bidderRequest?.ortb2?.device || {}; let placementIdOverrideFromGetParam = this.getPlacementIdOverrideFromGetParam(); // null or string let schain = null; + var auctionId = deepAccess(validBidRequests, '0.ortb2.source.tid'); + if (auctionId === '0') { + auctionId = null; + } let tosendtags = validBidRequests.map(ozoneBidRequest => { var obj = {}; let placementId = placementIdOverrideFromGetParam || this.getPlacementId(ozoneBidRequest); // prefer to use a valid override param, else the bidRequest placement Id @@ -305,6 +333,21 @@ export const spec = { if (gpid) { deepSetValue(obj, 'ext.gpid', gpid); } + let transactionId = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.tid'); + if (transactionId) { + obj.ext[whitelabelBidder].transactionId = transactionId; // this is the transactionId PER adUnit, common across bidders for this unit + } + if (auctionId) { + obj.ext[whitelabelBidder].auctionId = auctionId; // we were sent a valid auctionId to use - this will also be used as the root id value for the request + } + if (fledgeEnabled) { // fledge is enabled at some config level - pbjs.setBidderConfig or pbjs.setConfig + const auctionEnvironment = deepAccess(ozoneBidRequest, 'ortb2Imp.ext.ae'); // this will be set for one of 3 reasons; adunit, setBidderConfig, setConfig + if (isInteger(auctionEnvironment)) { + deepSetValue(obj, 'ext.ae', auctionEnvironment); + } else { + logError('ortb2Imp.ext.ae is not an integer - ignoring it for obj.id=' + obj.id); + } + } return obj; }); let extObj = {}; @@ -313,8 +356,8 @@ export const spec = { extObj[whitelabelBidder][whitelabelPrefix + '_rw'] = placementIdOverrideFromGetParam ? 1 : 0; if (validBidRequests.length > 0) { let userIds = this.cookieSyncBag.userIdObject; // 2021-01-06 - slight optimisation - we've already found this info - if (userIds.hasOwnProperty('pubcid')) { - extObj[whitelabelBidder].pubcid = userIds.pubcid; + if (userIds.hasOwnProperty('pubcid.org')) { + extObj[whitelabelBidder].pubcid = userIds['pubcid.org']; } } extObj[whitelabelBidder].pv = this.getPageId(); // attach the page ID that will be common to all auction calls for this page if refresh() is called @@ -362,6 +405,10 @@ export const spec = { } else { logInfo('WILL NOT ADD USP consent info; no bidderRequest.uspConsent.'); } + if (bidderRequest?.ortb2?.regs?.gpp) { + deepSetValue(ozoneRequest, 'regs.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(ozoneRequest, 'regs.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); + } if (schain) { // we set this while iterating over the bids logInfo('schain found'); deepSetValue(ozoneRequest, 'source.ext.schain', schain); @@ -369,15 +416,20 @@ export const spec = { if (config.getConfig('coppa') === true) { deepSetValue(ozoneRequest, 'regs.coppa', 1); } - let ozUuid = generateUUID(); - if (this.isBatchRequests()) { + extObj[whitelabelBidder].cookieDeprecationLabel = deepAccess(bidderRequest, 'ortb2.device.ext.cdep', 'none'); + logInfo('cookieDeprecationLabel from bidderRequest object = ' + extObj[whitelabelBidder].cookieDeprecationLabel); + let batchRequestsVal = this.getBatchRequests(); // false|numeric + if (typeof batchRequestsVal === 'number') { logInfo('going to batch the requests'); let arrRet = []; // return an array of objects containing data describing max 10 bids - for (let i = 0; i < tosendtags.length; i += 10) { - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) - ozoneRequest.imp = tosendtags.slice(i, i + 10); - ozoneRequest.ext = extObj; + for (let i = 0; i < tosendtags.length; i += batchRequestsVal) { + ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequest, 'source.tid', auctionId); + } + ozoneRequest.imp = tosendtags.slice(i, i + batchRequestsVal); + ozoneRequest.ext = extObj; if (ozoneRequest.imp.length > 0) { arrRet.push({ method: 'POST', @@ -393,17 +445,20 @@ export const spec = { logInfo('requests will not be batched.'); if (singleRequest) { logInfo('buildRequests starting to generate response for a single request'); - ozoneRequest.id = ozUuid; // Unique ID of the bid request, provided by the exchange. (REQUIRED) + ozoneRequest.id = generateUUID(); // Unique ID of the bid request, provided by the exchange. (REQUIRED) ozoneRequest.imp = tosendtags; ozoneRequest.ext = extObj; deepSetValue(ozoneRequest, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequest, 'source.tid', auctionId); + } var ret = { method: 'POST', url: this.getAuctionUrl(), data: JSON.stringify(ozoneRequest), bidderRequest: bidderRequest }; - logInfo('buildRequests request data for single = ', JSON.parse(JSON.stringify(ozoneRequest))); + logInfo('buildRequests request data for single = ', deepClone(ozoneRequest)); this.propertyBag.buildRequestsEnd = new Date().getTime(); logInfo(`buildRequests going to return for single at time ${this.propertyBag.buildRequestsEnd} (took ${this.propertyBag.buildRequestsEnd - this.propertyBag.buildRequestsStart}ms): `, ret); return ret; @@ -415,6 +470,9 @@ export const spec = { ozoneRequestSingle.imp = [imp]; ozoneRequestSingle.ext = extObj; deepSetValue(ozoneRequestSingle, 'user.ext.eids', userExtEids); + if (auctionId) { + deepSetValue(ozoneRequestSingle, 'source.tid', auctionId); + } logInfo('buildRequests RequestSingle (for non-single) = ', ozoneRequestSingle); return { method: 'POST', @@ -436,15 +494,15 @@ export const spec = { logInfo('getFloorObjectForAuction mediaTypesSizes : ', mediaTypesSizes); let ret = {}; if (mediaTypesSizes.banner) { - ret.banner = bidRequestRef.getFloor({mediaType: 'banner', currency: 'USD', size: 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}); + 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}); + ret.native = bidRequestRef.getFloor({mediaType: 'native', currency: 'USD', size: mediaTypesSizes.native}) || {}; } - logInfo('getFloorObjectForAuction returning : ', JSON.parse(JSON.stringify(ret))); + logInfo('getFloorObjectForAuction returning : ', deepClone(ret)); return ret; }, interpretResponse(serverResponse, request) { @@ -453,7 +511,7 @@ export const spec = { let whitelabelBidder = this.propertyBag.whitelabel.bidder; // by default = ozone let whitelabelPrefix = this.propertyBag.whitelabel.keyPrefix; logInfo(`interpretResponse time: ${startTime} . Time between buildRequests done and interpretResponse start was ${startTime - this.propertyBag.buildRequestsEnd}ms`); - logInfo(`serverResponse, request`, JSON.parse(JSON.stringify(serverResponse)), JSON.parse(JSON.stringify(request))); + logInfo(`serverResponse, request`, deepClone(serverResponse), deepClone(request)); serverResponse = serverResponse.body || {}; let aucId = serverResponse.id; // this will be correct for single requests and non-single if (!serverResponse.hasOwnProperty('seatbid')) { @@ -480,7 +538,7 @@ export const spec = { for (let j = 0; j < sb.bid.length; j++) { let thisRequestBid = this.getBidRequestForBidId(sb.bid[j].impid, request.bidderRequest.bids); logInfo(`seatbid:${i}, bid:${j} Going to set default w h for seatbid/bidRequest`, sb.bid[j], thisRequestBid); - const {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); + let {defaultWidth, defaultHeight} = defaultSize(thisRequestBid); let thisBid = ozoneAddStandardProperties(sb.bid[j], defaultWidth, defaultHeight); thisBid.meta = {advertiserDomains: thisBid.adomain || []}; let videoContext = null; @@ -512,8 +570,8 @@ export const spec = { this.setBidMediaTypeIfNotExist(thisBid, BANNER); } if (enhancedAdserverTargeting) { - let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid); - logInfo('Going to iterate allBidsForThisBidId', allBidsForThisBidid); + let allBidsForThisBidid = ozoneGetAllBidsForBidId(thisBid.bidId, serverResponse.seatbid, defaultWidth, defaultHeight); + logInfo('Going to iterate allBidsForThisBidId', deepClone(allBidsForThisBidid)); Object.keys(allBidsForThisBidid).forEach((bidderName, index, ar2) => { logInfo(`adding adserverTargeting for ${bidderName} for bidId ${thisBid.bidId}`); adserverTargeting[whitelabelPrefix + '_' + bidderName] = bidderName; @@ -521,6 +579,7 @@ export const spec = { 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); + adserverTargeting[whitelabelPrefix + '_' + bidderName + '_size'] = String(allBidsForThisBidid[bidderName].width) + 'x' + String(allBidsForThisBidid[bidderName].height); if (allBidsForThisBidid[bidderName].hasOwnProperty('dealid')) { adserverTargeting[whitelabelPrefix + '_' + bidderName + '_dealid'] = String(allBidsForThisBidid[bidderName].dealid); } @@ -550,6 +609,7 @@ export const spec = { } } let {seat: winningSeat, bid: winningBid} = ozoneGetWinnerForRequestBid(thisBid.bidId, serverResponse.seatbid); + winningBid = ozoneAddStandardProperties(winningBid, defaultWidth, defaultHeight); adserverTargeting[whitelabelPrefix + '_auc_id'] = String(aucId); // was request.bidderRequest.auctionId adserverTargeting[whitelabelPrefix + '_winner'] = String(winningSeat); adserverTargeting[whitelabelPrefix + '_bid'] = 'true'; @@ -571,10 +631,28 @@ export const spec = { arrAllBids.push(thisBid); } } + let ret = arrAllBids; + let fledgeAuctionConfigs = deepAccess(serverResponse, 'ext.igi') || []; // 20240606 standardising + if (Array.isArray(fledgeAuctionConfigs) && fledgeAuctionConfigs.length > 0) { + fledgeAuctionConfigs = fledgeAuctionConfigs.filter(config => { + if (!this.isValidAuctionConfig(config)) { + logWarn('Malformed auction config detected:', config); + return false; + } + return true; + }); + ret = { + bids: arrAllBids, + fledgeAuctionConfigs, + }; + } let endTime = new Date().getTime(); logInfo(`interpretResponse going to return at time ${endTime} (took ${endTime - startTime}ms) Time from buildRequests Start -> interpretRequests End = ${endTime - this.propertyBag.buildRequestsStart}ms`); - logInfo('interpretResponse arrAllBids (serialised): ', JSON.parse(JSON.stringify(arrAllBids))); // this is ok to log because the renderer has not been attached yet - return arrAllBids; + logInfo('interpretResponse arrAllBids (serialised): ', deepClone(ret)); // this is ok to log because the renderer has not been attached yet + return ret; + }, + isValidAuctionConfig(config) { + return typeof config === 'object' && config !== null; }, setBidMediaTypeIfNotExist(thisBid, mediaType) { if (!thisBid.hasOwnProperty('mediaType')) { @@ -613,11 +691,12 @@ export const spec = { } return ret; }, - getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy) { + getUserSyncs(optionsType, serverResponse, gdprConsent, usPrivacy, gppConsent = {}) { logInfo('getUserSyncs optionsType', optionsType, 'serverResponse', serverResponse, 'gdprConsent', gdprConsent, 'usPrivacy', usPrivacy, 'cookieSyncBag', this.cookieSyncBag); if (!serverResponse || serverResponse.length === 0) { return []; } + let { gppString = '', applicableSections = [] } = gppConsent; if (optionsType.iframeEnabled) { var arrQueryString = []; if (config.getConfig('debug')) { @@ -626,6 +705,10 @@ export const spec = { arrQueryString.push('gdpr=' + (deepAccess(gdprConsent, 'gdprApplies', false) ? '1' : '0')); arrQueryString.push('gdpr_consent=' + deepAccess(gdprConsent, 'consentString', '')); arrQueryString.push('usp_consent=' + (usPrivacy || '')); + arrQueryString.push('gpp=' + gppString); + if (Array.isArray(applicableSections)) { + arrQueryString.push(`gpp_sid=${applicableSections.join()}`); + } for (let keyname in this.cookieSyncBag.userIdObject) { arrQueryString.push(keyname + '=' + this.cookieSyncBag.userIdObject[keyname]); } @@ -659,47 +742,26 @@ export const spec = { } return null; }, - findAllUserIds(bidRequest) { - var ret = {}; - 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)) { - if (typeof (bidRequest.userId[key]) == 'string') { - ret[key] = bidRequest.userId[key]; - } else if (typeof (bidRequest.userId[key]) == 'object') { - logError(`WARNING: findAllUserIds had to use first key in user object to get value for bid.userId key: ${key}. Prebid adapter should be updated.`); - ret[key] = bidRequest.userId[key][Object.keys(bidRequest.userId[key])[0]]; // cannot use Object.values - } else { - logError(`failed to get string key value for userId : ${key}`); - } - } - } - let lipbid = deepAccess(bidRequest.userId, 'lipb.lipbid'); - if (lipbid) { - ret['lipb'] = {'lipbid': lipbid}; - } - let id5id = deepAccess(bidRequest.userId, 'id5id.uid'); - if (id5id) { - ret['id5id'] = id5id; - } - let parrableId = deepAccess(bidRequest.userId, 'parrableId.eid'); - if (parrableId) { - ret['parrableId'] = parrableId; - } - let sharedid = deepAccess(bidRequest.userId, 'sharedid.id'); - if (sharedid) { - ret['sharedid'] = sharedid; - } + findAllUserIdsFromEids(bidRequest) { + let ret = {}; + if (!bidRequest.hasOwnProperty('userIdAsEids')) { + logInfo('findAllUserIdsFromEids - no bidRequest.userIdAsEids object - will quit'); + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + } + for (let obj of bidRequest.userIdAsEids) { + ret[obj.source] = deepAccess(obj, 'uids.0.id'); } + this.tryGetPubCidFromOldLocation(ret, bidRequest); // legacy + return ret; + }, + tryGetPubCidFromOldLocation(ret, bidRequest) { if (!ret.hasOwnProperty('pubcid')) { let pubcid = deepAccess(bidRequest, 'crumbs.pubcid'); if (pubcid) { - ret['pubcid'] = pubcid; // if built with old pubCommonId module + ret['pubcid.org'] = pubcid; // if built with old pubCommonId module (use the new eid key) } } - return ret; }, getPlacementId(bidRequest) { return (bidRequest.params.placementId).toString(); @@ -767,7 +829,7 @@ export const spec = { return ret; }, _unpackVideoConfigIntoIABformat(ret, objConfig) { - let arrVideoKeysAllowed = ['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']; + let arrVideoKeysAllowed = ['mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h', 'startdelay', 'placement', 'plcmt', 'linearity', 'skip', 'skipmin', 'skipafter', 'sequence', 'battr', 'maxextended', 'minbitrate', 'maxbitrate', 'boxingallowed', 'playbackmethod', 'playbackend', 'delivery', 'pos', 'companionad', 'api', 'companiontype']; for (const key in objConfig) { var found = false; arrVideoKeysAllowed.forEach(function(arg) { @@ -795,11 +857,9 @@ export const spec = { return objRet; }, _addVideoDefaults(objRet, objConfig, addIfMissing) { - let context = deepAccess(objConfig, 'context'); - if (context === 'outstream') { - objRet.placement = 3; - } else if (context === 'instream') { - objRet.placement = 1; + let placementValue = this.getVideoPlacementValue(deepAccess(objConfig, 'context')); + if (placementValue) { + objRet.placement = placementValue; } let skippable = deepAccess(objConfig, 'skippable', null); if (skippable == null) { @@ -834,7 +894,9 @@ export const spec = { params: bid.params, price: bid.price, transactionId: bid.transactionId, - ttl: bid.ttl + ttl: bid.ttl, + ortb2: deepAccess(bid, 'ortb2'), + ortb2Imp: deepAccess(bid, 'ortb2Imp'), }; if (bid.hasOwnProperty('floorData')) { logObj.floorData = bid.floorData; @@ -843,7 +905,7 @@ export const spec = { } }; export function injectAdIdsIntoAllBidResponses(seatbid) { - logInfo('injectAdIdsIntoAllBidResponses', seatbid); + logInfo('injectAdIdsIntoAllBidResponses', deepClone(seatbid)); for (let i = 0; i < seatbid.length; i++) { let sb = seatbid[i]; for (let j = 0; j < sb.bid.length; j++) { @@ -895,7 +957,7 @@ export function ozoneGetWinnerForRequestBid(requestBidId, serverResponseSeatBid) } return {'seat': winningSeat, 'bid': thisBidWinner}; } -export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { +export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid, defaultWidth, defaultHeight) { let objBids = {}; for (let j = 0; j < serverResponseSeatBid.length; j++) { let theseBids = serverResponseSeatBid[j].bid; @@ -904,10 +966,11 @@ export function ozoneGetAllBidsForBidId(matchBidId, serverResponseSeatBid) { 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 if (objBids[thisSeat]['price'] < theseBids[k].price) { - objBids[thisSeat] = theseBids[k]; + objBids[thisSeat] = ozoneAddStandardProperties(theseBids[k], defaultWidth, defaultHeight); } } else { objBids[thisSeat] = theseBids[k]; + objBids[thisSeat] = ozoneAddStandardProperties(theseBids[k], defaultWidth, defaultHeight); } } } @@ -1039,7 +1102,7 @@ function newRenderer(adUnitCode, rendererOptions = {}) { } function outstreamRender(bid) { logInfo('outstreamRender called. Going to push the call to window.ozoneVideo.outstreamRender(bid) bid = (first static, then reference)'); - logInfo(JSON.parse(JSON.stringify(spec.getLoggableBidObject(bid)))); + logInfo(deepClone(spec.getLoggableBidObject(bid))); bid.renderer.push(() => { logInfo('Going to execute window.ozoneVideo.outstreamRender'); window.ozoneVideo.outstreamRender(bid); diff --git a/modules/paapi.js b/modules/paapi.js index 9122ecce1a0..8b214ebe135 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -2,15 +2,27 @@ * Collect PAAPI component auction configs from bid adapters and make them available through `pbjs.getPAAPIConfig()` */ import {config} from '../src/config.js'; -import {getHook, module} from '../src/hook.js'; -import {deepSetValue, logInfo, logWarn, mergeDeep, parseSizesInput} from '../src/utils.js'; +import {getHook, hook, module} from '../src/hook.js'; +import { + deepAccess, + deepEqual, + deepSetValue, + logError, + logInfo, + logWarn, + mergeDeep, + sizesToSizeTuples +} from '../src/utils.js'; import {IMP, PBS, registerOrtbProcessor, RESPONSE} from '../src/pbjsORTB.js'; import * as events from '../src/events.js'; import {EVENTS} from '../src/constants.js'; import {currencyCompare} from '../libraries/currencyUtils/currency.js'; -import {maximum, minimum} from '../src/utils/reducers.js'; -import {auctionManager} from '../src/auctionManager.js'; +import {keyCompare, maximum, minimum} from '../src/utils/reducers.js'; import {getGlobal} from '../src/prebidGlobal.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; +import {adapterMetrics, guardTids} from '../src/adapters/bidderFactory.js'; +import {defer} from '../src/utils/promise.js'; +import {auctionManager} from '../src/auctionManager.js'; const MODULE = 'PAAPI'; @@ -19,32 +31,31 @@ const USED = new WeakSet(); export function registerSubmodule(submod) { submodules.push(submod); - submod.init && submod.init({getPAAPIConfig}); + submod.init && submod.init({ + getPAAPIConfig, + expandFilters + }); } module('paapi', registerSubmodule); -function auctionConfigs() { - const store = new WeakMap(); - return function (auctionId, init = {}) { - const auction = auctionManager.index.getAuction({auctionId}); - if (auction == null) return; - if (!store.has(auction)) { - store.set(auction, init); - } - return store.get(auction); - }; -} +/* auction configs as returned by getPAAPIConfigs */ +const configsForAuction = auctionStore(); + +/* auction configs returned by adapters, but waiting for end-of-auction signals before they're added to configsForAuction */ +const pendingConfigsForAuction = auctionStore(); + +/* igb returned by adapters, waiting for end-of-auction signals before they're merged into configForAuctions */ +const pendingBuyersForAuction = auctionStore(); + +/* for auction configs that were generated in parallel with auctions (and contain promises), their resolve/reject methods */ +const deferredConfigsForAuction = auctionStore(); -const pendingForAuction = auctionConfigs(); -const configsForAuction = auctionConfigs(); let latestAuctionForAdUnit = {}; let moduleConfig = {}; -['paapi', 'fledgeForGpt'].forEach(ns => { - config.getConfig(ns, config => { - init(config[ns], ns); - }); +config.getConfig('paapi', config => { + init(config.paapi); }); export function reset() { @@ -52,22 +63,48 @@ export function reset() { latestAuctionForAdUnit = {}; } -export function init(cfg, configNamespace) { - if (configNamespace !== 'paapi') { - logWarn(`'${configNamespace}' configuration options will be renamed to 'paapi'; consider using setConfig({paapi: [...]}) instead`); - } +export function init(cfg) { if (cfg && cfg.enabled === true) { + if (!moduleConfig.enabled) { + attachHandlers(); + } moduleConfig = cfg; logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); } else { + if (moduleConfig.enabled) { + detachHandlers(); + } moduleConfig = {}; logInfo(`${MODULE} disabled`, cfg); } } -getHook('addComponentAuction').before(addComponentAuctionHook); -getHook('makeBidRequests').after(markForFledge); -events.on(EVENTS.AUCTION_END, onAuctionEnd); +function attachHandlers() { + getHook('addPaapiConfig').before(addPaapiConfigHook); + getHook('makeBidRequests').before(addPaapiData); + getHook('makeBidRequests').after(markForFledge); + getHook('processBidderRequests').before(parallelPaapiProcessing); + events.on(EVENTS.AUCTION_INIT, onAuctionInit); + events.on(EVENTS.AUCTION_END, onAuctionEnd); +} + +function detachHandlers() { + getHook('addPaapiConfig').getHooks({hook: addPaapiConfigHook}).remove(); + getHook('makeBidRequests').getHooks({hook: addPaapiData}).remove(); + getHook('makeBidRequests').getHooks({hook: markForFledge}).remove(); + getHook('processBidderRequests').getHooks({hook: parallelPaapiProcessing}).remove(); + events.off(EVENTS.AUCTION_INIT, onAuctionInit); + events.off(EVENTS.AUCTION_END, onAuctionEnd); +} + +function getStaticSignals(adUnit = {}) { + const cfg = {}; + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + cfg.requestedSize = requestedSize; + } + return cfg; +} function getSlotSignals(bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; @@ -89,89 +126,281 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { return cfg; } +export function buyersToAuctionConfigs(igbRequests, merge = mergeBuyers, config = moduleConfig?.componentSeller ?? {}, partitioners = { + compact: (igbRequests) => partitionBuyers(igbRequests.map(req => req[1])).map(part => [{}, part]), + expand: partitionBuyersByBidder +}) { + if (!config.auctionConfig) { + logWarn(MODULE, 'Cannot use IG buyers: paapi.componentSeller.auctionConfig not set', igbRequests.map(req => req[1])); + return []; + } + const partition = partitioners[config.separateAuctions ? 'expand' : 'compact']; + return partition(igbRequests) + .map(([request, igbs]) => { + const auctionConfig = mergeDeep(merge(igbs), config.auctionConfig); + auctionConfig.auctionSignals = setFPD(auctionConfig.auctionSignals || {}, request); + return [request, auctionConfig]; + }); +} + function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adUnits}) { - const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []) + const adUnitsByCode = Object.fromEntries(adUnits?.map(au => [au.code, au]) || []); const allReqs = bidderRequests?.flatMap(br => br.bids); - const paapiConfigs = {}; + const paapiConfigs = configsForAuction(auctionId); (adUnitCodes || []).forEach(au => { - paapiConfigs[au] = null; + if (!paapiConfigs.hasOwnProperty(au)) { + paapiConfigs[au] = null; + } !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); }); - Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => { - const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); - paapiConfigs[adUnitCode] = { - ...slotSignals, - componentAuctions: auctionConfigs.map(cfg => mergeDeep({}, slotSignals, cfg)) - }; - // TODO: need to flesh out size treatment: - // - which size should the paapi auction pick? (this uses the first one defined) - // - should we signal it to SSPs, and how? - // - what should we do if adapters pick a different one? - // - what does size mean for video and native? - const size = parseSizesInput(adUnitsByCode[adUnitCode]?.mediaTypes?.banner?.sizes)?.[0]?.split('x'); - if (size) { - paapiConfigs[adUnitCode].requestedSize = { - width: size[0], - height: size[1], - }; + + const pendingConfigs = pendingConfigsForAuction(auctionId); + const pendingBuyers = pendingBuyersForAuction(auctionId); + + if (pendingConfigs && pendingBuyers) { + Object.entries(pendingBuyers).forEach(([adUnitCode, igbRequests]) => { + buyersToAuctionConfigs(igbRequests).forEach(([{bidder}, auctionConfig]) => append(pendingConfigs, adUnitCode, {id: getComponentSellerConfigId(bidder), config: auctionConfig})) + }) + } + + const deferredConfigs = deferredConfigsForAuction(auctionId); + + const adUnitsWithConfigs = Array.from(new Set(Object.keys(pendingConfigs).concat(Object.keys(deferredConfigs)))); + const signals = Object.fromEntries( + adUnitsWithConfigs.map(adUnitCode => { + latestAuctionForAdUnit[adUnitCode] = auctionId; + const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; + return [adUnitCode, { + ...getStaticSignals(adUnitsByCode[adUnitCode]), + ...getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)) + }] + }) + ) + + const configsById = {}; + Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { + auctionConfigs.forEach(({id, config}) => append(configsById, id, { + adUnitCode, + config: mergeDeep({}, signals[adUnitCode], config) + })); + }); + + function resolveSignals(signals, deferrals) { + Object.entries(deferrals).forEach(([signal, {resolve, default: defaultValue}]) => { + let value = signals.hasOwnProperty(signal) ? signals[signal] : null; + if (value == null && defaultValue == null) { + value = undefined; + } else if (typeof defaultValue === 'object' && typeof value === 'object') { + value = mergeDeep({}, defaultValue, value); + } else { + value = value ?? defaultValue + } + resolve(value); + }) + } + + Object.entries(deferredConfigs).forEach(([adUnitCode, {top, components}]) => { + resolveSignals(signals[adUnitCode], top); + Object.entries(components).forEach(([configId, {deferrals}]) => { + const matchingConfigs = configsById.hasOwnProperty(configId) ? configsById[configId] : []; + if (matchingConfigs.length > 1) { + logWarn(`Received multiple PAAPI configs for the same bidder and seller (${configId}), active PAAPI auctions will only see the first`); + } + const {config} = matchingConfigs.shift() ?? {config: {...signals[adUnitCode]}} + resolveSignals(config, deferrals); + }) + }); + + const newConfigs = Object.values(configsById).flatMap(configs => configs); + const hasDeferredConfigs = Object.keys(deferredConfigs).length > 0; + + if (moduleConfig.parallel && hasDeferredConfigs && newConfigs.length > 0) { + logError(`Received PAAPI configs after PAAPI auctions were already started in parallel with their contextual auction`, newConfigs) + } + + newConfigs.forEach(({adUnitCode, config}) => { + if (paapiConfigs[adUnitCode] == null) { + paapiConfigs[adUnitCode] = { + ...signals[adUnitCode], + componentAuctions: [] + } } - latestAuctionForAdUnit[adUnitCode] = auctionId; + paapiConfigs[adUnitCode].componentAuctions.push(mergeDeep({}, signals[adUnitCode], config)); }); - configsForAuction(auctionId, paapiConfigs); - submodules.forEach(submod => submod.onAuctionConfig?.( - auctionId, - paapiConfigs, - (adUnitCode) => paapiConfigs[adUnitCode] != null && USED.add(paapiConfigs[adUnitCode])) - ); + + if (!moduleConfig.parallel || !hasDeferredConfigs) { + submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, paapiConfigs)); + } } -function setFPDSignals(auctionConfig, fpd) { - auctionConfig.auctionSignals = mergeDeep({}, {prebid: fpd}, auctionConfig.auctionSignals); +function append(target, key, value) { + !target.hasOwnProperty(key) && (target[key] = []); + target[key].push(value); } -export function addComponentAuctionHook(next, request, componentAuctionConfig) { - if (getFledgeConfig().enabled) { - const {adUnitCode, auctionId, ortb2, ortb2Imp} = request; - const configs = pendingForAuction(auctionId); - if (configs != null) { - setFPDSignals(componentAuctionConfig, {ortb2, ortb2Imp}); - !configs.hasOwnProperty(adUnitCode) && (configs[adUnitCode] = []); - configs[adUnitCode].push(componentAuctionConfig); - } else { - logWarn(MODULE, `Received component auction config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, componentAuctionConfig); +function setFPD(target, {ortb2, ortb2Imp}) { + ortb2 != null && deepSetValue(target, 'prebid.ortb2', mergeDeep({}, ortb2, target.prebid?.ortb2)); + ortb2Imp != null && deepSetValue(target, 'prebid.ortb2Imp', mergeDeep({}, ortb2Imp, target.prebid?.ortb2Imp)); + return target; +} + +function getConfigId(bidderCode, seller) { + return `${bidderCode}::${seller}`; +} + +function getComponentSellerConfigId(bidderCode) { + return moduleConfig.componentSeller.separateAuctions ? `igb::${bidderCode}` : 'igb'; +} + +export function addPaapiConfigHook(next, request, paapiConfig) { + if (getFledgeConfig(config.getCurrentBidder()).enabled) { + const {adUnitCode, auctionId, bidder} = request; + + // eslint-disable-next-line no-inner-declarations + function storePendingData(store, data) { + const target = store(auctionId); + if (target != null) { + append(target, adUnitCode, data) + } else { + logWarn(MODULE, `Received PAAPI config for auction that has closed (auction '${auctionId}', adUnit '${adUnitCode}')`, data); + } + } + + const {config, igb} = paapiConfig; + if (config) { + config.auctionSignals = setFPD(config.auctionSignals || {}, request); + const pbs = config.perBuyerSignals = config.perBuyerSignals ?? {}; + (config.interestGroupBuyers || []).forEach(buyer => { + pbs[buyer] = setFPD(pbs[buyer] ?? {}, request); + }) + storePendingData(pendingConfigsForAuction, {id: getConfigId(bidder, config.seller), config}); + } + if (igb && checkOrigin(igb)) { + igb.pbs = setFPD(igb.pbs || {}, request); + storePendingData(pendingBuyersForAuction, [request, igb]) + } + } + next(request, paapiConfig); +} + +export const IGB_TO_CONFIG = { + cur: 'perBuyerCurrencies', + pbs: 'perBuyerSignals', + ps: 'perBuyerPrioritySignals', + maxbid: 'auctionSignals.prebid.perBuyerMaxbid', +} + +function checkOrigin(igb) { + if (igb.origin) return true; + logWarn('PAAPI buyer does not specify origin and will be ignored', igb); +} + +/** + * Convert a list of InterestGroupBuyer (igb) objects into a partial auction config. + * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + */ +export function mergeBuyers(igbs) { + const buyers = new Set(); + return Object.assign( + igbs.reduce((config, igb) => { + if (checkOrigin(igb)) { + if (!buyers.has(igb.origin)) { + buyers.add(igb.origin); + Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { + if (igb[igbField] != null) { + const entry = deepAccess(config, configField) || {} + entry[igb.origin] = igb[igbField]; + deepSetValue(config, configField, entry); + } + }); + } else { + logWarn(MODULE, `Duplicate buyer: ${igb.origin}. All but the first will be ignored`, igbs); + } + } + return config; + }, {}), + { + interestGroupBuyers: Array.from(buyers.keys()) + } + ); +} + +/** + * Partition a list of InterestGroupBuyer (igb) object into sets that can each be merged into a single auction. + * If the same buyer (origin) appears more than once, it will be split across different partition unless the igb objects + * are identical. + */ +export function partitionBuyers(igbs) { + return igbs.reduce((partitions, igb) => { + if (checkOrigin(igb)) { + let partition = partitions.find(part => !part.hasOwnProperty(igb.origin) || deepEqual(part[igb.origin], igb)); + if (!partition) { + partition = {}; + partitions.push(partition); + } + partition[igb.origin] = igb; } + return partitions; + }, []).map(part => Object.values(part)); +} + +export function partitionBuyersByBidder(igbRequests) { + const requests = {}; + const igbs = {}; + igbRequests.forEach(([request, igb]) => { + !requests.hasOwnProperty(request.bidder) && (requests[request.bidder] = request); + append(igbs, request.bidder, igb); + }) + return Object.entries(igbs).map(([bidder, igbs]) => [requests[bidder], igbs]) +} + +/** + * Expand PAAPI api filters into a map from ad unit code to auctionId. + * + * @param auctionId when specified, the result will have this as the value for each entry. + * when not specified, each ad unit will map to the latest auction that involved that ad unit. + * @param adUnitCode when specified, the result will contain only one entry (for this ad unit) or be empty (if this ad + * unit was never involved in an auction). + * when not specified, the result will contain an entry for every ad unit that was involved in any auction. + * @return {{[adUnitCode: string]: string}} + */ +function expandFilters({auctionId, adUnitCode} = {}) { + let adUnitCodes = []; + if (adUnitCode == null) { + adUnitCodes = Object.keys(latestAuctionForAdUnit); + } else if (latestAuctionForAdUnit.hasOwnProperty(adUnitCode)) { + adUnitCodes = [adUnitCode]; } - next(request, componentAuctionConfig); + return Object.fromEntries( + adUnitCodes.map(au => [au, auctionId ?? latestAuctionForAdUnit[au]]) + ); } /** * Get PAAPI auction configuration. * - * @param auctionId? optional auction filter; if omitted, the latest auction for each ad unit is used - * @param adUnitCode? optional ad unit filter - * @param includeBlanks if true, include null entries for ad units that match the given filters but do not have any available auction configs. - * @returns {{}} a map from ad unit code to auction config for the ad unit. + * @param {Object} [filters] - Filters object + * @param {string} [filters.auctionId] optional auction filter; if omitted, the latest auction for each ad unit is used + * @param {string} [filters.adUnitCode] optional ad unit filter + * @param {boolean} [includeBlanks=false] if true, include null entries for ad units that match the given filters but do not have any available auction configs. + * @returns {Object} a map from ad unit code to auction config for the ad unit. */ -export function getPAAPIConfig({auctionId, adUnitCode} = {}, includeBlanks = false) { +export function getPAAPIConfig(filters = {}, includeBlanks = false) { const output = {}; - const targetedAuctionConfigs = auctionId && configsForAuction(auctionId); - Object.keys((auctionId != null ? targetedAuctionConfigs : latestAuctionForAdUnit) ?? []).forEach(au => { - const latestAuctionId = latestAuctionForAdUnit[au]; - const auctionConfigs = targetedAuctionConfigs ?? (latestAuctionId && configsForAuction(latestAuctionId)); - if ((adUnitCode ?? au) === au) { - let candidate; - if (targetedAuctionConfigs?.hasOwnProperty(au)) { - candidate = targetedAuctionConfigs[au]; - } else if (auctionId == null && auctionConfigs?.hasOwnProperty(au)) { - candidate = auctionConfigs[au]; - } + Object.entries(expandFilters(filters)).forEach(([au, auctionId]) => { + const auctionConfigs = configsForAuction(auctionId); + if (auctionConfigs?.hasOwnProperty(au)) { + // ad unit was involved in a PAAPI auction + const candidate = auctionConfigs[au]; if (candidate && !USED.has(candidate)) { output[au] = candidate; USED.add(candidate); } else if (includeBlanks) { output[au] = null; } + } else if (auctionId == null && includeBlanks) { + // ad unit was involved in a non-PAAPI auction + output[au] = null; } }); return output; @@ -183,73 +412,333 @@ function isFledgeSupported() { return 'runAdAuction' in navigator && 'joinAdInterestGroup' in navigator; } -function getFledgeConfig() { - const bidder = config.getCurrentBidder(); - const useGlobalConfig = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); +function getFledgeConfig(bidder) { + const enabled = moduleConfig.enabled && (bidder == null || !moduleConfig.bidders?.length || moduleConfig.bidders?.includes(bidder)); return { - enabled: config.getConfig('fledgeEnabled') ?? useGlobalConfig, - ae: config.getConfig('defaultForSlots') ?? (useGlobalConfig ? moduleConfig.defaultForSlots : undefined) + enabled, + ae: enabled ? moduleConfig.defaultForSlots : undefined }; } +/** + * Given an array of size tuples, return the one that should be used for PAAPI. + */ +export const getPAAPISize = hook('sync', function (sizes) { + sizes = sizes + ?.filter(([w, h]) => !(w === h && w <= 5)); + + if (sizes?.length) { + return sizes + .reduce(maximum(keyCompare(([w, h]) => w * h))); + } +}, 'getPAAPISize'); + +function getRequestedSize(adUnit) { + return adUnit.ortb2Imp?.ext?.paapi?.requestedSize || (() => { + const size = getPAAPISize(sizesToSizeTuples(adUnit.mediaTypes?.banner?.sizes)); + if (size) { + return { + width: size[0], + height: size[1] + }; + } + })(); +} + +export function addPaapiData(next, adUnits, ...args) { + if (isFledgeSupported() && moduleConfig.enabled) { + adUnits.forEach(adUnit => { + // https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/Protected%20Audience%20Support.md + const igsAe = adUnit.ortb2Imp?.ext?.igs != null + ? adUnit.ortb2Imp.ext.igs.ae || 1 + : null; + const extAe = adUnit.ortb2Imp?.ext?.ae; + if (igsAe !== extAe && igsAe != null && extAe != null) { + logWarn(MODULE, `Ad unit defines conflicting ortb2Imp.ext.ae and ortb2Imp.ext.igs, using the latter`, adUnit); + } + const ae = igsAe ?? extAe ?? moduleConfig.defaultForSlots; + if (ae) { + deepSetValue(adUnit, 'ortb2Imp.ext.ae', ae); + adUnit.ortb2Imp.ext.igs = Object.assign({ + ae: ae, + biddable: 1 + }, adUnit.ortb2Imp.ext.igs); + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + deepSetValue(adUnit, 'ortb2Imp.ext.paapi.requestedSize', requestedSize); + } + adUnit.bids.forEach(bidReq => { + if (!getFledgeConfig(bidReq.bidder).enabled) { + deepSetValue(bidReq, 'ortb2Imp.ext.ae', 0); + bidReq.ortb2Imp.ext.igs = {ae: 0, biddable: 0}; + } + }) + } + }) + } + next(adUnits, ...args); +} + export function markForFledge(next, bidderRequests) { if (isFledgeSupported()) { bidderRequests.forEach((bidderReq) => { - config.runWithBidder(bidderReq.bidderCode, () => { - const {enabled, ae} = getFledgeConfig(); - Object.assign(bidderReq, {fledgeEnabled: enabled}); - bidderReq.bids.forEach(bidReq => { - deepSetValue(bidReq, 'ortb2Imp.ext.ae', bidReq.ortb2Imp?.ext?.ae ?? ae); - }); + const {enabled} = getFledgeConfig(bidderReq.bidderCode); + Object.assign(bidderReq, { + paapi: { + enabled, + componentSeller: !!moduleConfig.componentSeller?.auctionConfig + } }); }); } next(bidderRequests); } +export const ASYNC_SIGNALS = ['auctionSignals', 'sellerSignals', 'perBuyerSignals', 'perBuyerTimeouts', 'directFromSellerSignals']; + +const validatePartialConfig = (() => { + const REQUIRED_SYNC_SIGNALS = [ + { + props: ['seller'], + validate: (val) => typeof val === 'string' + }, + { + props: ['interestGroupBuyers'], + validate: (val) => Array.isArray(val) && val.length > 0 + }, + { + props: ['decisionLogicURL', 'decisionLogicUrl'], + validate: (val) => typeof val === 'string' + } + ]; + + return function (config) { + const invalid = REQUIRED_SYNC_SIGNALS.find(({props, validate}) => props.every(prop => !config.hasOwnProperty(prop) || !config[prop] || !validate(config[prop]))); + if (invalid) { + logError(`Partial PAAPI config has missing or invalid property "${invalid.props[0]}"`, config) + return false; + } + return true; + } +})() + +/** + * Adapters can provide a `spec.buildPAAPIConfigs(validBidRequests, bidderRequest)` to be included in PAAPI auctions + * that can be started in parallel with contextual auctions. + * + * If PAAPI is enabled, and an adapter provides `buildPAAPIConfigs`, it is invoked just before `buildRequests`, + * and takes the same arguments. It should return an array of PAAPI configuration objects with the same format + * as in `interpretResponse` (`{bidId, config?, igb?}`). + * + * Everything returned by `buildPAAPIConfigs` is treated in the same way as if it was returned by `interpretResponse` - + * except for signals that can be provided asynchronously (cfr. `ASYNC_SIGNALS`), which are replaced by promises. + * When the (contextual) auction ends, the promises are resolved. + * + * If during the auction the adapter's `interpretResponse` returned matching configurations (same `bidId`, + * and a `config` with the same `seller`, or an `igb` with the same `origin`), the promises resolve to their contents. + * Otherwise, they resolve to the values provided by `buildPAAPIConfigs`, or an empty object if no value was provided. + * + * Promisified auction configs are available from `getPAAPIConfig` immediately after `requestBids`. + * If the `paapi.parallel` config flag is set, PAAPI submodules are also triggered at the same time + * (instead of when the auction ends). + */ +export function parallelPaapiProcessing(next, spec, bids, bidderRequest, ...args) { + function makeDeferrals(defaults = {}) { + let promises = {}; + const deferrals = Object.fromEntries(ASYNC_SIGNALS.map(signal => { + const def = defer({promiseFactory: (resolver) => new Promise(resolver)}); + def.default = defaults.hasOwnProperty(signal) ? defaults[signal] : null; + promises[signal] = def.promise; + return [signal, def] + })) + return [deferrals, promises]; + } + + const {auctionId, paapi: {enabled, componentSeller} = {}} = bidderRequest; + const auctionConfigs = configsForAuction(auctionId); + bids.map(bid => bid.adUnitCode).forEach(adUnitCode => { + latestAuctionForAdUnit[adUnitCode] = auctionId; + if (!auctionConfigs.hasOwnProperty(adUnitCode)) { + auctionConfigs[adUnitCode] = null; + } + }); + + if (enabled && spec.buildPAAPIConfigs) { + const metrics = adapterMetrics(bidderRequest); + const tidGuard = guardTids(bidderRequest); + let partialConfigs; + metrics.measureTime('buildPAAPIConfigs', () => { + try { + partialConfigs = spec.buildPAAPIConfigs(bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest)) + } catch (e) { + logError(`Error invoking "buildPAAPIConfigs":`, e); + } + }); + const requestsById = Object.fromEntries(bids.map(bid => [bid.bidId, bid])); + (partialConfigs ?? []).forEach(({bidId, config, igb}) => { + const bidRequest = requestsById.hasOwnProperty(bidId) && requestsById[bidId]; + if (!bidRequest) { + logError(`Received partial PAAPI config for unknown bidId`, {bidId, config}); + } else { + const adUnitCode = bidRequest.adUnitCode; + latestAuctionForAdUnit[adUnitCode] = auctionId; + const deferredConfigs = deferredConfigsForAuction(auctionId); + + const getDeferredConfig = () => { + if (!deferredConfigs.hasOwnProperty(adUnitCode)) { + const [deferrals, promises] = makeDeferrals(); + auctionConfigs[adUnitCode] = { + ...getStaticSignals(auctionManager.index.getAdUnit(bidRequest)), + ...promises, + componentAuctions: [] + } + deferredConfigs[adUnitCode] = { + top: deferrals, + components: {}, + auctionConfig: auctionConfigs[adUnitCode] + } + } + return deferredConfigs[adUnitCode]; + } + + if (config && validatePartialConfig(config)) { + const configId = getConfigId(bidRequest.bidder, config.seller); + const deferredConfig = getDeferredConfig(); + if (deferredConfig.components.hasOwnProperty(configId)) { + logWarn(`Received multiple PAAPI configs for the same bidder and seller; config will be ignored`, { + config, + bidder: bidRequest.bidder + }) + } else { + const [deferrals, promises] = makeDeferrals(config); + const auctionConfig = { + ...getStaticSignals(bidRequest), + ...config, + ...promises + } + deferredConfig.auctionConfig.componentAuctions.push(auctionConfig) + deferredConfig.components[configId] = {auctionConfig, deferrals}; + } + } + if (componentSeller && igb && checkOrigin(igb)) { + const configId = getComponentSellerConfigId(spec.code); + const deferredConfig = getDeferredConfig(); + const partialConfig = buyersToAuctionConfigs([[bidRequest, igb]])[0][1]; + if (deferredConfig.components.hasOwnProperty(configId)) { + const {auctionConfig, deferrals} = deferredConfig.components[configId]; + if (!auctionConfig.interestGroupBuyers.includes(igb.origin)) { + const immediate = {}; + Object.entries(partialConfig).forEach(([key, value]) => { + if (deferrals.hasOwnProperty(key)) { + mergeDeep(deferrals[key], {default: value}); + } else { + immediate[key] = value; + } + }) + mergeDeep(auctionConfig, immediate); + } else { + logWarn(`Received the same PAAPI buyer multiple times for the same PAAPI auction. Consider setting paapi.componentSeller.separateAuctions: true`, igb) + } + } else { + const [deferrals, promises] = makeDeferrals(partialConfig); + const auctionConfig = { + ...partialConfig, + ...getStaticSignals(bidRequest), + ...promises, + } + deferredConfig.components[configId] = {auctionConfig, deferrals}; + deferredConfig.auctionConfig.componentAuctions.push(auctionConfig); + } + } + } + }) + } + return next.call(this, spec, bids, bidderRequest, ...args); +} + +export function onAuctionInit({auctionId}) { + if (moduleConfig.parallel) { + auctionManager.index.getAuction({auctionId}).requestsDone.then(() => { + if (Object.keys(deferredConfigsForAuction(auctionId)).length > 0) { + submodules.forEach(submod => submod.onAuctionConfig?.(auctionId, configsForAuction(auctionId))); + } + }) + } +} + export function setImpExtAe(imp, bidRequest, context) { - if (imp.ext?.ae && !context.bidderRequest.fledgeEnabled) { + if (!context.bidderRequest.paapi?.enabled) { delete imp.ext?.ae; + delete imp.ext?.igs; } } registerOrtbProcessor({type: IMP, name: 'impExtAe', fn: setImpExtAe}); -// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up -// fledge response processing in two steps: first aggregate all the auction configs by their imp... +export function parseExtIgi(response, ortbResponse, context) { + paapiResponseParser( + (ortbResponse.ext?.igi || []).flatMap(igi => { + return (igi?.igs || []).map(igs => { + if (igs.impid !== igi.impid && igs.impid != null && igi.impid != null) { + logWarn(MODULE, 'ORTB response ext.igi.igs.impid conflicts with parent\'s impid', igi); + } + return { + config: igs.config, + impid: igs.impid ?? igi.impid + } + }).concat((igi?.igb || []).map(igb => ({ + igb, + impid: igi.impid + }))) + }), + response, + context + ) +} -export function parseExtPrebidFledge(response, ortbResponse, context) { - (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []).forEach((cfg) => { - const impCtx = context.impContext[cfg.impid]; +function paapiResponseParser(configs, response, context) { + configs.forEach((config) => { + const impCtx = context.impContext[config.impid]; if (!impCtx?.imp?.ext?.ae) { - logWarn('Received fledge auction configuration for an impression that was not in the request or did not ask for it', cfg, impCtx?.imp); + logWarn(MODULE, 'Received auction configuration for an impression that was not in the request or did not ask for it', config, impCtx?.imp); } else { - impCtx.fledgeConfigs = impCtx.fledgeConfigs || []; - impCtx.fledgeConfigs.push(cfg); + impCtx.paapiConfigs = impCtx.paapiConfigs || []; + impCtx.paapiConfigs.push(config); } }); } +// to make it easier to share code between the PBS adapter and adapters whose backend is PBS, break up +// fledge response processing in two steps: first aggregate all the auction configs by their imp... + +export function parseExtPrebidFledge(response, ortbResponse, context) { + paapiResponseParser( + (ortbResponse.ext?.prebid?.fledge?.auctionconfigs || []), + response, + context + ) +} + registerOrtbProcessor({type: RESPONSE, name: 'extPrebidFledge', fn: parseExtPrebidFledge, dialects: [PBS]}); +registerOrtbProcessor({type: RESPONSE, name: 'extIgiIgs', fn: parseExtIgi}); // ...then, make them available in the adapter's response. This is the client side version, for which the // interpretResponse api is {fledgeAuctionConfigs: [{bidId, config}]} -export function setResponseFledgeConfigs(response, ortbResponse, context) { +export function setResponsePaapiConfigs(response, ortbResponse, context) { const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => ({ + .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => ({ bidId: impCtx.bidRequest.bidId, - config: cfg.config + ...cfg }))); if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; + response.paapi = configs; } } registerOrtbProcessor({ type: RESPONSE, - name: 'fledgeAuctionConfigs', + name: 'paapiConfigs', priority: -1, - fn: setResponseFledgeConfigs, - dialects: [PBS] + fn: setResponsePaapiConfigs, }); diff --git a/modules/paapiForGpt.js b/modules/paapiForGpt.js new file mode 100644 index 00000000000..61133014e28 --- /dev/null +++ b/modules/paapiForGpt.js @@ -0,0 +1,165 @@ +/** + * GPT-specific slot configuration logic for PAAPI. + */ +import {getHook, submodule} from '../src/hook.js'; +import {deepAccess, logInfo, logWarn, sizeTupleToSizeString} from '../src/utils.js'; +import {config} from '../src/config.js'; +import {getGlobal} from '../src/prebidGlobal.js'; + +import {keyCompare} from '../src/utils/reducers.js'; +import {getGPTSlotsForAdUnits, targeting} from '../src/targeting.js'; + +const MODULE = 'paapiForGpt'; + +let getPAAPIConfig; + +config.getConfig('paapi', (cfg) => { + if (deepAccess(cfg, 'paapi.gpt.configWithTargeting', true)) { + logInfo(MODULE, 'enabling PAAPI configuration with setTargetingForGPTAsync') + targeting.setTargetingForGPT.before(setTargetingHook); + } else { + targeting.setTargetingForGPT.getHooks({hook: setTargetingHook}).remove(); + } +}); + +export function setTargetingHookFactory(setPaapiConfig = getGlobal().setPAAPIConfigForGPT) { + return function(next, adUnit, customSlotMatching) { + const adUnitCodes = Array.isArray(adUnit) ? adUnit : [adUnit] + adUnitCodes + .map(adUnitCode => adUnitCode == null ? undefined : {adUnitCode}) + .forEach(filters => setPaapiConfig(filters, customSlotMatching)) + next(adUnit, customSlotMatching); + } +} + +export function slotConfigurator() { + const PREVIOUSLY_SET = {}; + return function setComponentAuction(adUnitCode, gptSlots, auctionConfigs, reset = true) { + if (gptSlots.length > 0) { + let previous = PREVIOUSLY_SET[adUnitCode] ?? {}; + let configsBySeller = Object.fromEntries(auctionConfigs.map(cfg => [cfg.seller, cfg])); + const sellers = Object.keys(configsBySeller); + if (reset) { + configsBySeller = Object.assign(previous, configsBySeller); + previous = Object.fromEntries(sellers.map(seller => [seller, null])); + } else { + sellers.forEach(seller => { + previous[seller] = null; + }); + } + Object.keys(previous).length ? PREVIOUSLY_SET[adUnitCode] = previous : delete PREVIOUSLY_SET[adUnitCode]; + const componentAuction = Object.entries(configsBySeller) + .map(([configKey, auctionConfig]) => ({configKey, auctionConfig})); + if (componentAuction.length > 0) { + gptSlots.forEach(gptSlot => { + gptSlot.setConfig({componentAuction}); + logInfo(MODULE, `register component auction configs for: ${adUnitCode}: ${gptSlot.getAdUnitPath()}`, auctionConfigs); + }); + } + } else if (auctionConfigs.length > 0) { + logWarn(MODULE, `unable to register component auction config for ${adUnitCode}`, auctionConfigs); + } + }; +} + +const setComponentAuction = slotConfigurator(); + +export const getPAAPISizeHook = (() => { + /* + https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing#faq + https://support.google.com/admanager/answer/1100453?hl=en + + Ignore any placeholder sizes, where placeholder is defined as a square creative with a side of <= 5 pixels + Look if there are any sizes that are part of the set of supported ad sizes defined here. If there are, choose the largest supported size by area (width * height) + For clarity, the set of supported ad sizes includes all of the ad sizes listed under “Top-performing ad sizes”, “Other supported ad sizes”, and “Regional ad sizes”. + If not, choose the largest remaining size (i.e. that isn’t in the list of supported ad sizes) by area (width * height) + */ + const SUPPORTED_SIZES = [ + [728, 90], + [336, 280], + [300, 250], + [300, 50], + [160, 600], + [1024, 768], + [970, 250], + [970, 90], + [768, 1024], + [480, 320], + [468, 60], + [320, 480], + [320, 100], + [320, 50], + [300, 600], + [300, 100], + [250, 250], + [234, 60], + [200, 200], + [180, 150], + [125, 125], + [120, 600], + [120, 240], + [120, 60], + [88, 31], + [980, 120], + [980, 90], + [950, 90], + [930, 180], + [750, 300], + [750, 200], + [750, 100], + [580, 400], + [250, 360], + [240, 400], + ].sort(keyCompare(([w, h]) => -(w * h))) + .map(size => [size, sizeTupleToSizeString(size)]); + + return function(next, sizes) { + if (sizes?.length) { + const sizeStrings = new Set(sizes.map(sizeTupleToSizeString)); + const preferredSize = SUPPORTED_SIZES.find(([_, sizeStr]) => sizeStrings.has(sizeStr)); + if (preferredSize) { + next.bail(preferredSize[0]); + return; + } + } + next(sizes); + } +})(); + +export function setPAAPIConfigFactory( + getConfig = (filters) => getPAAPIConfig(filters, true), + setGptConfig = setComponentAuction, + getSlots = getGPTSlotsForAdUnits) { + /** + * Configure GPT slots with PAAPI auction configs. + * `filters` are the same filters accepted by `pbjs.getPAAPIConfig`; + */ + return function(filters = {}, customSlotMatching) { + let some = false; + const cfg = getConfig(filters) || {}; + const auToSlots = getSlots(Object.keys(cfg), customSlotMatching); + + Object.entries(cfg).forEach(([au, config]) => { + if (config != null) { + some = true; + } + setGptConfig(au, auToSlots[au], config?.componentAuctions || [], true); + }) + if (!some) { + logInfo(`${MODULE}: No component auctions available to set`); + } + } +} +/** + * Configure GPT slots with PAAPI component auctions. Accepts the same filter arguments as `pbjs.getPAAPIConfig`. + */ +getGlobal().setPAAPIConfigForGPT = setPAAPIConfigFactory(); +const setTargetingHook = setTargetingHookFactory(); + +submodule('paapi', { + name: 'gpt', + init(params) { + getPAAPIConfig = params.getPAAPIConfig; + getHook('getPAAPISize').before(getPAAPISizeHook); + } +}); diff --git a/modules/fledgeForGpt.md b/modules/paapiForGpt.md similarity index 55% rename from modules/fledgeForGpt.md rename to modules/paapiForGpt.md index 28f44da6459..31cde2e268d 100644 --- a/modules/fledgeForGpt.md +++ b/modules/paapiForGpt.md @@ -1,22 +1,22 @@ # Overview -This module allows Prebid.js to support FLEDGE by integrating it with GPT's [experimental FLEDGE +This module allows Prebid.js to support PAAPI by integrating it with GPT's [experimental PAAPI support](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing). -To learn more about FLEDGE in general, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md). +To learn more about PAAPI in general, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md). -This document covers the steps necessary for publishers to enable FLEDGE on their inventory. It also describes -the changes Bid Adapters need to implement in order to support FLEDGE. +This document covers the steps necessary for publishers to enable PAAPI on their inventory. It also describes +the changes Bid Adapters need to implement in order to support PAAPI. ## Publisher Integration -Publishers wishing to enable FLEDGE support must do two things. First, they must compile Prebid.js with support for this module. -This is accomplished by adding the `fledgeForGpt` module to the list of modules they are already using: +Publishers wishing to enable PAAPI support must do two things. First, they must compile Prebid.js with support for this module. +This is accomplished by adding the `paapiForGpt` module to the list of modules they are already using: ``` -gulp build --modules=fledgeForGpt,... +gulp build --modules=paapiForGpt,... ``` -Second, they must enable FLEDGE in their Prebid.js configuration. -This is done through module level configuration, but to provide a high degree of flexiblity for testing, FLEDGE settings also exist at the bidder level and slot level. +Second, they must enable PAAPI in their Prebid.js configuration. +This is done through module level configuration, but to provide a high degree of flexiblity for testing, PAAPI settings also exist the slot level. ### Module Configuration This module exposes the following settings: @@ -27,14 +27,13 @@ This module exposes the following settings: |bidders | Array[String] |Optional list of bidders |Defaults to all bidders | |defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1 | -As noted above, FLEDGE support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). -using the `setConfig` method of Prebid.js. Optionally, a list of -bidders to apply these settings to may be provided: +As noted above, PAAPI support is disabled by default. To enable it, set the `enabled` value to `true` for this module and configure `defaultForSlots` to be `1` (meaning _Client-side auction_). +using the `setConfig` method of Prebid.js. Optionally, a list of bidders to apply these settings to may be provided: ```js pbjs.que.push(function() { pbjs.setConfig({ - fledgeForGpt: { + paapi: { enabled: true, bidders: ['openx', 'rtbhouse'], defaultForSlots: 1 @@ -43,35 +42,14 @@ pbjs.que.push(function() { }); ``` -### Bidder Configuration -This module adds the following setting for bidders: - -|Name |Type |Description |Notes | -| :------------ | :------------ | :------------ |:------------ | -| fledgeEnabled | Boolean | Enable/disable a bidder to participate in FLEDGE | Defaults to `false` | -|defaultForSlots | Number |Default value for `imp.ext.ae` in requests for specified bidders |Should be 1| - -Individual bidders may be further included or excluded here using the `setBidderConfig` method -of Prebid.js: - -```js -pbjs.setBidderConfig({ - bidders: ["openx"], - config: { - fledgeEnabled: true, - defaultForSlots: 1 - } -}); -``` - ### AdUnit Configuration -All adunits can be opted-in to FLEDGE in the global config via the `defaultForSlots` parameter. +All adunits can be opted-in to PAAPI in the global config via the `defaultForSlots` parameter. If needed, adunits can be configured individually by setting an attribute of the `ortb2Imp` object for that adunit. This attribute will take precedence over `defaultForSlots` setting. |Name |Type |Description |Notes | | :------------ | :------------ | :------------ |:------------ | -| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates FLEDGE eligible, 0 indicates it is not | Absence indicates this is not FLEDGE eligible | +| ortb2Imp.ext.ae | Integer | Auction Environment: 1 indicates PAAPI eligible, 0 indicates it is not | Absence indicates this is not PAAPI eligible | The `ae` field stands for Auction Environment and was chosen to be consistent with the field that GAM passes to bidders in their Open Bidding and Exchange Bidding APIs. More details on that can be found @@ -91,31 +69,31 @@ pbjs.addAdUnits({ ``` ## Bid Adapter Integration -Chrome has enabled a two-tier auction in FLEDGE. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with +Chrome has enabled a two-tier auction in PAAPI. This allows multiple sellers (frequently SSPs) to act on behalf of the publisher with a single entity serving as the final decision maker. In their [current approach](https://github.com/google/ads-privacy/tree/master/proposals/fledge-multiple-seller-testing), GPT has opted to run the final auction layer while allowing other SSPs/sellers to participate as -[Component Auctions](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) which feed their -bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#24-scoring-bids-in-component-auctions). +[Component Auctions](https://github.com/WICG/turtledove/blob/main/PAAPI.md#21-initiating-an-on-device-auction) which feed their +bids to the final layer. To learn more about Component Auctions, go [here](https://github.com/WICG/turtledove/blob/main/PAAPI.md#24-scoring-bids-in-component-auctions). -The FLEDGE auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given -seller. This module enables FLEDGE support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an +The PAAPI auction, including Component Auctions, are configured via an `AuctionConfig` object that defines the parameters of the auction for a given +seller. This module enables PAAPI support by allowing bid adaptors to return `AuctionConfig` objects in addition to bids. If a bid adaptor returns an `AuctionConfig` object, Prebid.js will register it with the appropriate GPT ad slot so the bidder can participate as a Component Auction in the overall -FLEDGE auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). +PAAPI auction for that slot. More details on the GPT API can be found [here](https://developers.google.com/publisher-tag/reference#googletag.config.componentauctionconfig). -Modifying a bid adapter to support FLEDGE is a straightforward process and consists of the following steps: -1. Detecting when a bid request is FLEDGE eligible +Modifying a bid adapter to support PAAPI is a straightforward process and consists of the following steps: +1. Detecting when a bid request is PAAPI eligible 2. Responding with AuctionConfig -FLEDGE eligibility is made available to bid adapters through the `bidderRequest.fledgeEnabled` field. +PAAPI eligibility is made available to bid adapters through the `bidderRequest.paapi.enabled` field. The [`bidderRequest`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#bidderrequest-parameters) object is passed to the [`buildRequests`](https://docs.prebid.org/dev-docs/bidder-adaptor.html#building-the-request) method of an adapter. Bid adapters -who wish to participate should read this flag and pass it to their server. FLEDGE eligibility depends on a number of parameters: +who wish to participate should read this flag and pass it to their server. PAAPI eligibility depends on a number of parameters: 1. Chrome enablement 2. Publisher participatipon in the [Origin Trial](https://developer.chrome.com/docs/privacy-sandbox/unified-origin-trial/#configure) 3. Publisher Prebid.js configuration (detailed above) -When a bid request is FLEDGE enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: +When a bid request is PAAPI enabled, a bid adapter can return a tuple consisting of bids and AuctionConfig objects rather than just a list of bids: ```js function interpretResponse(resp, req) { @@ -138,8 +116,8 @@ An AuctionConfig must be associated with an adunit and auction, and this is acco `validBidRequests` array passed to the `buildRequests` function - see [here](https://docs.prebid.org/dev-docs/bidder-adaptor.html#ad-unit-params-in-the-validbidrequests-array) for more details. This means that the AuctionConfig objects returned from `interpretResponse` must contain a `bidId` field whose value corresponds to the request it should be associated with. This may raise the question: why isn't the AuctionConfig object returned as part of the bid? The -answer is that it's possible to participate in the FLEDGE auction without returning a contextual bid. +answer is that it's possible to participate in the PAAPI auction without returning a contextual bid. An example of this can be seen in the OpenX OpenRTB bid adapter [here](https://github.com/prebid/Prebid.js/blob/master/modules/openxOrtbBidAdapter.js#L327). -Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in FLEDGE. The details of creating an AuctionConfig object are beyond the scope of this document. +Other than the addition of the `bidId` field, the AuctionConfig object should adhere to the requirements set forth in PAAPI. The details of creating an AuctionConfig object are beyond the scope of this document. diff --git a/modules/pairIdSystem.js b/modules/pairIdSystem.js index dbff4c6a402..778857bae1c 100644 --- a/modules/pairIdSystem.js +++ b/modules/pairIdSystem.js @@ -50,9 +50,11 @@ export const pairIdSubmodule = { return value && Array.isArray(value) ? {'pairId': value} : undefined }, /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @returns {id: string | undefined } + * Performs action to obtain ID and return a value in the callback's response argument. + * @function getId + * @param {Object} config - The configuration object. + * @param {Object} config.params - The parameters from the configuration. + * @returns {{id: string[] | undefined}} The obtained IDs or undefined if no IDs are found. */ getId(config) { const pairIdsString = pairIdFromLocalStorage(PAIR_ID_KEY) || pairIdFromCookie(PAIR_ID_KEY) @@ -67,13 +69,28 @@ export const pairIdSubmodule = { const configParams = (config && config.params) || {}; if (configParams && configParams.liveramp) { - let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY - const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation) - try { - const obj = JSON.parse(atob(liverampValue)); - ids = ids.concat(obj.envelope); - } catch (error) { - logInfo(error) + let LRStorageLocation = configParams.liveramp.storageKey || DEFAULT_LIVERAMP_PAIR_ID_KEY; + const liverampValue = pairIdFromLocalStorage(LRStorageLocation) || pairIdFromCookie(LRStorageLocation); + + if (liverampValue) { + try { + const parsedValue = atob(liverampValue); + if (parsedValue) { + const obj = JSON.parse(parsedValue); + + if (obj && typeof obj === 'object' && obj.envelope) { + ids = ids.concat(obj.envelope); + } else { + logInfo('Pairid: Parsed object is not valid or does not contain envelope'); + } + } else { + logInfo('Pairid: Decoded value is empty'); + } + } catch (error) { + logInfo('Pairid: Error parsing JSON: ', error); + } + } else { + logInfo('Pairid: liverampValue for pairId from storage is empty or null'); } } diff --git a/modules/parrableIdSystem.js b/modules/parrableIdSystem.js deleted file mode 100644 index 5651bdf0434..00000000000 --- a/modules/parrableIdSystem.js +++ /dev/null @@ -1,416 +0,0 @@ -/** - * This module adds Parrable to the User ID module - * The {@link module:modules/userId} module is required - * @module modules/parrableIdSystem - * @requires module:modules/userId - */ - -// ci trigger: 1 - -import { - contains, - deepClone, - inIframe, - isEmpty, - isPlainObject, - logError, - logWarn, - pick, - timestamp -} from '../src/utils.js'; -import {find} from '../src/polyfill.js'; -import {ajax} from '../src/ajax.js'; -import {submodule} from '../src/hook.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {uspDataHandler} from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_UID} from '../src/activities/modules.js'; - -/** - * @typedef {import('../modules/userId/index.js').Submodule} Submodule - * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig - * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData - */ - -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 MODULE_NAME = 'parrableId'; - -const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}); - -function getExpirationDate() { - const oneYearFromNow = new Date(timestamp() + ONE_YEAR_MS); - return oneYearFromNow.toGMTString(); -} - -function deserializeParrableId(parrableIdStr) { - const parrableId = {}; - const values = parrableIdStr.split(','); - - values.forEach(function(value) { - const pair = value.split(':'); - 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(parrableIdAndParams) { - let components = []; - - if (parrableIdAndParams.eid) { - components.push('eid:' + parrableIdAndParams.eid); - } - if (parrableIdAndParams.ibaOptout) { - components.push('ibaOptout:1'); - } - if (parrableIdAndParams.ccpaOptout) { - components.push('ccpaOptout:1'); - } - if (parrableIdAndParams.tpcSupport !== undefined) { - const tpcSupportComponent = parrableIdAndParams.tpcSupport === true ? 'tpc:1' : 'tpc:0'; - const tpcUntil = `tpcUntil:${parrableIdAndParams.tpcUntil}`; - components.push(tpcSupportComponent); - components.push(tpcUntil); - } - if (parrableIdAndParams.filteredUntil) { - components.push(`filteredUntil:${parrableIdAndParams.filteredUntil}`); - components.push(`filterHits:${parrableIdAndParams.filterHits}`); - } - - return components.join(','); -} - -function isValidConfig(configParams) { - if (!configParams) { - logError('User ID - parrableId submodule requires configParams'); - return false; - } - if (!configParams.partners && !configParams.partner) { - logError('User ID - parrableId submodule requires partner list'); - return false; - } - if (configParams.storage) { - logWarn('User ID - parrableId submodule does not require a storage config'); - } - 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) { - 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(parrableIdAndParams) { - if (parrableIdAndParams) { - const parrableIdStr = encodeURIComponent(serializeParrableId(parrableIdAndParams)); - storage.setCookie(PARRABLE_COOKIE_NAME, parrableIdStr, getExpirationDate(), 'lax'); - } -} - -function readLegacyCookies() { - const eid = storage.getCookie(LEGACY_ID_COOKIE_NAME); - const ibaOptout = (storage.getCookie(LEGACY_OPTOUT_COOKIE_NAME) === 'true'); - if (eid || ibaOptout) { - const parrableId = {}; - if (eid) { - parrableId.eid = eid; - } - if (ibaOptout) { - parrableId.ibaOptout = ibaOptout; - } - return parrableId; - } - return null; -} - -function migrateLegacyCookies(parrableId) { - if (parrableId) { - writeCookie(parrableId); - if (parrableId.eid) { - storage.setCookie(LEGACY_ID_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); - } - if (parrableId.ibaOptout) { - storage.setCookie(LEGACY_OPTOUT_COOKIE_NAME, '', EXPIRE_COOKIE_DATE); - } - } -} - -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 (isEmpty(config.allowedZones) && - isEmpty(config.allowedOffsets)) { - return true; - } - if (isZoneListed(config.allowedZones, zone)) { - return true; - } - if (contains(config.allowedOffsets, offset)) { - return true; - } - return false; - } - - function isBlocked() { - if (isEmpty(config.blockedZones) && - isEmpty(config.blockedOffsets)) { - return false; - } - if (isZoneListed(config.blockedZones, zone)) { - return true; - } - if (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, params } = readCookie() || {}; - if (!parrableId) { - parrableId = readLegacyCookies(); - migrateLegacyCookies(parrableId); - } - - 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, - url: refererInfo.page, - prebidVersion: '$prebid.version$', - isIframe: inIframe(), - tpcSupport - }; - - if (shouldFilter === false) { - data.filterHits = params.filterHits; - } - - const searchParams = { - data: encodeBase64UrlSafe(btoa(JSON.stringify(data))), - gdpr: gdprApplies ? 1 : 0, - _rand: Math.random() - }; - - if (uspString) { - searchParams.us_privacy = uspString; - } - - if (gdprApplies) { - searchParams.gdpr_consent = gdprConsentString; - } - - const options = { - method: 'GET', - withCredentials: true - }; - - const callback = function (cb) { - const callbacks = { - success: response => { - let newParrableId = parrableId ? deepClone(parrableId) : {}; - let newParams = {}; - if (response) { - try { - let responseObj = JSON.parse(response); - if (responseObj) { - if (responseObj.ccpaOptout !== true) { - newParrableId.eid = responseObj.eid; - } else { - newParrableId.eid = null; - newParrableId.ccpaOptout = true; - } - 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) { - logError(error); - cb(); - } - writeCookie({ ...newParrableId, ...newParams }); - cb(newParrableId); - } else { - logError('parrableId: ID fetch returned an empty result'); - cb(); - } - }, - error: error => { - logError(`parrableId: ID fetch encountered an error`, error); - cb(); - } - }; - - if (shouldFilter) { - incrementFilterHits(parrableId, params); - } else { - ajax(PARRABLE_URL, callbacks, searchParams, options); - } - }; - - return { - callback, - id: parrableId - }; -} - -/** @type {Submodule} */ -export const parrableIdSubmodule = { - /** - * used to link submodule with config - * @type {string} - */ - name: MODULE_NAME, - /** - * Global Vendor List ID - * @type {number} - */ - gvlid: PARRABLE_GVLID, - - /** - * decode the stored id value for passing to bid requests - * @function - * @param {ParrableId} parrableId - * @return {(Object|undefined} - */ - decode(parrableId) { - if (parrableId && isPlainObject(parrableId)) { - return { parrableId }; - } - return undefined; - }, - - /** - * performs action to obtain id and return a value in the callback's response argument - * @function - * @param {SubmoduleConfig} [config] - * @param {ConsentData} [consentData] - * @returns {function(callback:function), id:ParrableId} - */ - getId(config, gdprConsentData, currentStoredId) { - const configParams = (config && config.params) || {}; - return fetchId(configParams, gdprConsentData); - }, - eids: { - 'parrableId': { - source: 'parrable.com', - 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 = pick(parrableId, [ - 'ibaOptout', - 'ccpaOptout' - ]); - if (Object.keys(extendedData).length) { - return extendedData; - } - } - }, - }, -}; - -submodule('userId', parrableIdSubmodule); diff --git a/modules/permutiveIdentityManagerIdSystem.js b/modules/permutiveIdentityManagerIdSystem.js new file mode 100644 index 00000000000..5dc12d44edb --- /dev/null +++ b/modules/permutiveIdentityManagerIdSystem.js @@ -0,0 +1,151 @@ +import {MODULE_TYPE_UID} from '../src/activities/modules.js' +import {submodule} from '../src/hook.js' +import {getStorageManager} from '../src/storageManager.js' +import {prefixLog, safeJSONParse} from '../src/utils.js' +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + * @typedef {import('../modules/userId/index.js').ConsentData} ConsentData + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +const MODULE_NAME = 'permutiveIdentityManagerId' +const PERMUTIVE_ID_DATA_STORAGE_KEY = 'permutive-prebid-id' + +const ID5_DOMAIN = 'id5-sync.com' +const LIVERAMP_DOMAIN = 'liveramp.com' +const UID_DOMAIN = 'uidapi.com' + +const PRIMARY_IDS = ['id5id', 'idl_env', 'uid2'] + +export const storage = getStorageManager({moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME}) + +const logger = prefixLog('[PermutiveID]') + +const readFromSdkLocalStorage = () => { + const data = safeJSONParse(storage.getDataFromLocalStorage(PERMUTIVE_ID_DATA_STORAGE_KEY)) + const id = {} + if (data && typeof data === 'object' && 'providers' in data && typeof data.providers === 'object') { + const now = Date.now() + for (const [idName, value] of Object.entries(data.providers)) { + if (PRIMARY_IDS.includes(idName) && value.userId) { + if (!value.expiryTime || value.expiryTime > now) { + id[idName] = value.userId + } + } + } + } + return id +} + +/** + * Catch and log errors + * @param {function} fn - Function to safely evaluate + */ +function makeSafe (fn) { + try { + return fn() + } catch (e) { + logger.logError(e) + } +} + +const waitAndRetrieveFromSdk = (timeoutMs) => + new Promise( + resolve => { + const fallback = setTimeout(() => { + logger.logInfo('timeout expired waiting for SDK - attempting read from local storage again') + resolve(readFromSdkLocalStorage()) + }, timeoutMs) + return window?.permutive?.ready(() => makeSafe(() => { + logger.logInfo('Permutive SDK is ready') + const onReady = makeSafe(() => window.permutive.addons.identity_manager.prebid.onReady) + if (typeof onReady === 'function') { + onReady((ids) => { + logger.logInfo('Permutive SDK has provided ids') + resolve(ids) + clearTimeout(fallback) + }) + } else { + logger.logError('Permutive SDK initialised but identity manager prebid api not present') + } + })) + } + ) + +/** @type {Submodule} */ +export const permutiveIdentityManagerIdSubmodule = { + /** + * 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 + * @param {SubmoduleConfig|undefined} config + * @returns {(Object|undefined)} + */ + decode(value, config) { + return value + }, + + /** + * performs action to obtain id and return a value in the callback's response argument + * @function getId + * @param {SubmoduleConfig} submoduleConfig + * @param {ConsentData} consentData + * @param {(Object|undefined)} cacheIdObj + * @returns {IdResponse|undefined} + */ + getId(submoduleConfig, consentData, cacheIdObj) { + const id = readFromSdkLocalStorage() + if (Object.entries(id).length > 0) { + logger.logInfo('found id in sdk storage') + return { id } + } else if ('params' in submoduleConfig && submoduleConfig.params.ajaxTimeout) { + logger.logInfo('failed to find id in sdk storage - waiting for sdk') + // Is ajaxTimeout an appropriate timeout to use here? + return { callback: (done) => waitAndRetrieveFromSdk(submoduleConfig.params.ajaxTimeout).then(done) } + } else { + logger.logInfo('failed to find id in sdk storage and no wait time specified') + } + }, + + primaryIds: PRIMARY_IDS, + + eids: { + 'id5id': { + getValue: function (data) { + return data.uid + }, + source: ID5_DOMAIN, + atype: 1, + getUidExt: function (data) { + if (data.ext) { + return data.ext + } + } + }, + 'idl_env': { + source: LIVERAMP_DOMAIN, + atype: 3, + }, + 'uid2': { + source: UID_DOMAIN, + atype: 3, + getValue: function(data) { + return data.id + }, + getUidExt: function(data) { + if (data.ext) { + return data.ext + } + } + } + } +} + +submodule('userId', permutiveIdentityManagerIdSubmodule) diff --git a/modules/permutiveIdentityManagerIdSystem.md b/modules/permutiveIdentityManagerIdSystem.md new file mode 100644 index 00000000000..ae249803d11 --- /dev/null +++ b/modules/permutiveIdentityManagerIdSystem.md @@ -0,0 +1,58 @@ +# Permutive Identity Manager + +This module supports [Permutive](https://permutive.com/) customers in using Permutive's Identity Manager functionality. + +To use this Prebid.js module it is assumed that the site includes Permutive's SDK, with Identity Manager configuration +enabled. See Permutive's user documentation for more information on Identity Manager. + +## Building Prebid.js with Permutive Identity Manager Support + +Prebid.js must be built with the `permutiveIdentityManagerIdSystem` module in order for Permutive's Identity Manager to be able to +activate relevant user identities to Prebid. + +To build Prebid.js with the `permutiveIdentityManagerIdSystem` module included: + +``` +gulp build --modules=userId,permutiveIdentityManagerIdSystem +``` + +## Prebid configuration + +There is minimal configuration required to be set on Prebid.js, since the bulk of the behaviour is managed through +Permutive's dashboard and SDK. + +It is recommended to keep the Prebid.js caching for this module short, since the mechanism by which Permutive's SDK +communicates with Prebid.js is effectively a local cache anyway. + +``` +pbjs.setConfig({ + ... + userSync: { + userIds: [ + { + name: 'permutiveIdentityManagerId', + params: { + ajaxTimeout: 90 + }, + storage: { + type: 'html5', + name: 'permutiveIdentityManagerId', + refreshInSeconds: 5 + } + } + ], + auctionDelay: 100 + }, + ... +}); +``` + +### ajaxTimeout + +By default this module will read IDs provided by the Permutive SDK from local storage when requested by prebid, and if +nothing is found, will not provide any identities. If a timeout is provided via the `ajaxTimeout` parameter, it will +instead wait for up to the specified number of milliseconds for Permutive's SDK to become available, and will retrieve +identities from the SDK directly if/when this happens. + +This value should be set to a value smaller than the `auctionDelay` set on the `userSync` configuration object, since +there is no point waiting longer than this as the auction will already have been triggered. \ No newline at end of file diff --git a/modules/permutiveRtdProvider.js b/modules/permutiveRtdProvider.js index c42a15d9197..bb2dff6189e 100644 --- a/modules/permutiveRtdProvider.js +++ b/modules/permutiveRtdProvider.js @@ -95,7 +95,8 @@ export function getModuleConfig(customModuleConfig) { /** * Sets ortb2 config for ac bidders * @param {Object} bidderOrtb2 - The ortb2 object for the all bidders - * @param {Object} customModuleConfig - Publisher config for module + * @param {Object} moduleConfig - Publisher config for module + * @param {Object} segmentData - Segment data grouped by bidder or type */ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { const acBidders = deepAccess(moduleConfig, 'params.acBidders') @@ -129,13 +130,13 @@ export function setBidderRtb (bidderOrtb2, moduleConfig, segmentData) { /** * Updates `user.data` object in existing bidder config with Permutive segments - * @param string bidder - The bidder + * @param {string} bidder - The bidder identifier * @param {Object} currConfig - Current bidder config - * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine - * the transformations on user data to include the ORTB2 object * @param {string[]} segmentIDs - Permutive segment IDs * @param {string[]} sspSegmentIDs - Permutive SSP segment IDs * @param {Object} topics - Privacy Sandbox Topics, keyed by IAB taxonomy version (600, 601, etc.) + * @param {Object[]} transformationConfigs - array of objects with `id` and `config` properties, used to determine + * the transformations on user data to include the ORTB2 object * @param {Object} segmentData - The segments available for targeting * @return {Object} Merged ortb2 object */ @@ -270,7 +271,7 @@ function setSegments (reqBidsConfigObj, moduleConfig, segmentData) { */ function makeSafe (fn) { try { - fn() + return fn() } catch (e) { logError(e) } @@ -310,23 +311,71 @@ export function isPermutiveOnPage () { * @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', []) - +export function getSegments(maxSegs) { const segments = { - ac: [..._pcrprs, ..._ppam, ...legacySegs], - ix: readSegments('_pindexs', []), - rubicon: readSegments('_prubicons', []), - appnexus: readSegments('_papns', []), - gam: readSegments('_pdfps', []), - ssp: readSegments('_pssps', { - cohorts: [], - ssps: [] + ac: + makeSafe(() => { + const legacySegs = + makeSafe(() => + readSegments('_psegs', []) + .map(Number) + .filter((seg) => seg >= 1000000) + .map(String), + ) || []; + const _ppam = makeSafe(() => readSegments('_ppam', []).map(String)) || []; + const _pcrprs = makeSafe(() => readSegments('_pcrprs', []).map(String)) || []; + + return [..._pcrprs, ..._ppam, ...legacySegs]; + }) || [], + + ix: + makeSafe(() => { + const _pindexs = readSegments('_pindexs', []); + return _pindexs.map(String); + }) || [], + + rubicon: + makeSafe(() => { + const _prubicons = readSegments('_prubicons', []); + return _prubicons.map(String); + }) || [], + + appnexus: + makeSafe(() => { + const _papns = readSegments('_papns', []); + return _papns.map(String); + }) || [], + + gam: + makeSafe(() => { + const _pdfps = readSegments('_pdfps', []); + return _pdfps.map(String); + }) || [], + + ssp: makeSafe(() => { + const _pssps = readSegments('_pssps', { + cohorts: [], + ssps: [], + }); + + return { + cohorts: makeSafe(() => _pssps.cohorts.map(String)) || [], + ssps: makeSafe(() => _pssps.ssps.map(String)) || [], + }; }), - topics: readSegments('_ppsts', {}), - } + + topics: + makeSafe(() => { + const _ppsts = readSegments('_ppsts', {}); + + const topics = {}; + for (const [k, value] of Object.entries(_ppsts)) { + topics[k] = makeSafe(() => value.map(String)) || []; + } + + return topics; + }) || {}, + }; for (const bidder in segments) { if (bidder === 'ssp') { @@ -342,7 +391,8 @@ export function getSegments (maxSegs) { } } - return segments + logger.logInfo(`Read segments`, segments) + return segments; } /** @@ -393,7 +443,7 @@ function iabSegmentId(permutiveSegmentId, iabIds) { * Pull the latest configuration and cohort information and update accordingly. * * @param reqBidsConfigObj - Bidder provided config for request - * @param customModuleConfig - Publisher provide config + * @param moduleConfig - Publisher provided config */ export function readAndSetCohorts(reqBidsConfigObj, moduleConfig) { const segmentData = getSegments(deepAccess(moduleConfig, 'params.maxSegs')) diff --git a/modules/pgamsspBidAdapter.js b/modules/pgamsspBidAdapter.js index fdc6bcf302f..36dbd1159cc 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -1,253 +1,21 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'pgamssp'; +const GVLID = 1353; const AD_URL = 'https://us-east.pgammedia.com/pbjs'; const SYNC_URL = 'https://cs.pgammedia.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor, - eids: [] - }; - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2?.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.id5id?.uid, 'id5-sync.com'); - } - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} - export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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, - tmax: bidderRequest.timeout - }; - - if (bidderRequest.uspConsent) { - request.ccpa = bidderRequest.uspConsent; - } - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - 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, gppConsent) => { - 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}`; - } - - if (gppConsent?.gppString && gppConsent?.applicableSections?.length) { - syncUrl += '&gpp=' + gppConsent.gppString; - syncUrl += '&gpp_sid=' + gppConsent.applicableSections.join(','); - } - - const coppa = config.getConfig('coppa') ? 1 : 0; - syncUrl += `&coppa=${coppa}`; - - return [{ - type: syncType, - url: syncUrl - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/pirIdSystem.md b/modules/pirIdSystem.md deleted file mode 100644 index 913804f85c4..00000000000 --- a/modules/pirIdSystem.md +++ /dev/null @@ -1,27 +0,0 @@ -# Overview - -Module Name: pirIDSystem -Module Type: UserID Module -Maintainer: pawel.grudzien@grupawp.pl - -# Description - -User identification system for WPM - -### Prebid Params example - -``` -pbjs.setConfig({ - userSync: { - userIds: [{ - name: 'pirID', - storage: { - type: 'cookie', - name: 'pirIdToken', - expires: 7, - refreshInSeconds: 360 - }, - }] - } -}); -``` diff --git a/modules/pixfutureBidAdapter.js b/modules/pixfutureBidAdapter.js index 1c3f9b8da1a..c7ed1ec989d 100644 --- a/modules/pixfutureBidAdapter.js +++ b/modules/pixfutureBidAdapter.js @@ -5,7 +5,6 @@ import {config} from '../src/config.js'; import {find, includes} from '../src/polyfill.js'; import {deepAccess, isArray, isFn, isNumber, isPlainObject} from '../src/utils.js'; import {auctionManager} from '../src/auctionManager.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; @@ -126,7 +125,7 @@ export const spec = { method: 'POST', options: {withCredentials: true}, data: { - v: getGlobal().version, + v: 'v' + '$prebid.version$', pageUrl: referer, bidId: bidRequest.bidId, // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 @@ -277,7 +276,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords) - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } diff --git a/modules/playdigoBidAdapter.js b/modules/playdigoBidAdapter.js index 6c4ea6492d9..5d65a91e1a7 100644 --- a/modules/playdigoBidAdapter.js +++ b/modules/playdigoBidAdapter.js @@ -1,199 +1,21 @@ -import { logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'playdigo'; +const GVLID = 1302; const AD_URL = 'https://server.playdigo.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, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!bid.getFloor || typeof bid.getFloor !== 'function') { - return 0; - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} +const SYNC_URL = 'https://cs.playdigo.com'; export const spec = { code: BIDDER_CODE, + gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - const placements = []; - const request = { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - tmax: config.getConfig('bidderTimeout') - }; - - if (bidderRequest.gdprConsent) { - request.gdpr = { - consentString: bidderRequest.gdprConsent.consentString - }; - } - - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent.gppString; - request.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - request.gpp = bidderRequest.ortb2.regs.gpp; - request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - 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; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/prebidServerBidAdapter/config.js b/modules/prebidServerBidAdapter/config.js index 87274504f64..4a5ac1d8564 100644 --- a/modules/prebidServerBidAdapter/config.js +++ b/modules/prebidServerBidAdapter/config.js @@ -11,7 +11,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' }, - timeout: 1000 + maxTimeout: 1000 }, 'rubicon': { adapter: 'prebidServer', @@ -24,20 +24,7 @@ export const S2S_VENDORS = { 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 + maxTimeout: 500 }, 'openwrap': { adapter: 'prebidServer', @@ -46,6 +33,6 @@ export const S2S_VENDORS = { p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' }, - timeout: 500 + maxTimeout: 500 } } diff --git a/modules/prebidServerBidAdapter/index.js b/modules/prebidServerBidAdapter/index.js index 037119d44a2..33ddb9847fa 100644 --- a/modules/prebidServerBidAdapter/index.js +++ b/modules/prebidServerBidAdapter/index.js @@ -4,7 +4,6 @@ import { deepClone, flatten, generateUUID, - getPrebidInternal, insertUserSyncIframe, isNumber, isPlainObject, @@ -16,16 +15,16 @@ import { triggerPixel, uniques, } from '../../src/utils.js'; -import { EVENTS, REJECTION_REASON, S2S } from '../../src/constants.js'; +import {EVENTS, REJECTION_REASON, S2S} from '../../src/constants.js'; import adapterManager, {s2sActivityParams} from '../../src/adapterManager.js'; import {config} from '../../src/config.js'; -import {addComponentAuction, isValid} from '../../src/adapters/bidderFactory.js'; +import {addPaapiConfig, isValid} from '../../src/adapters/bidderFactory.js'; import * as events from '../../src/events.js'; import {includes} from '../../src/polyfill.js'; import {S2S_VENDORS} from './config.js'; import {ajax} from '../../src/ajax.js'; import {hook} from '../../src/hook.js'; -import {hasPurpose1Consent} from '../../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../../src/utils/gdpr.js'; import {buildPBSRequest, interpretPBSResponse} from './ortbConverter.js'; import {useMetrics} from '../../src/utils/perfMetrics.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; @@ -37,8 +36,6 @@ const TYPE = S2S.SRC; let _syncCount = 0; let _s2sConfigs; -let eidPermissions; - /** * @typedef {Object} AdapterOptions * @summary s2sConfig parameter that adds arguments to resulting OpenRTB payload that goes to Prebid Server @@ -82,6 +79,7 @@ let eidPermissions; * @property {string} [syncEndpoint] endpoint URL for syncing cookies * @property {Object} [extPrebid] properties will be merged into request.ext.prebid * @property {Object} [ortbNative] base value for imp.native.request + * @property {Number} [maxTimeout] */ /** @@ -89,7 +87,6 @@ let eidPermissions; */ export const s2sDefaultConfig = { bidders: Object.freeze([]), - timeout: 1000, syncTimeout: 1000, maxBids: 1, adapter: 'prebidServer', @@ -100,7 +97,8 @@ export const s2sDefaultConfig = { eventtrackers: [ {event: 1, methods: [1, 2]} ], - } + }, + maxTimeout: 1500 }; config.setDefaults({ @@ -178,7 +176,7 @@ function setS2sConfig(options) { const activeBidders = []; const optionsValid = normalizedOptions.every((option, i, array) => { - formatUrlParams(options); + formatUrlParams(option); const updateSuccess = updateConfigDefaultVendor(option); if (updateSuccess !== false) { const valid = validateConfigRequiredProps(option); @@ -426,7 +424,12 @@ function bidWonHandler(bid) { } function getMatchingConsentUrl(urlProp, gdprConsent) { - return hasPurpose1Consent(gdprConsent) ? urlProp.p1Consent : urlProp.noP1Consent; + const hasPurpose = hasPurpose1Consent(gdprConsent); + const url = hasPurpose ? urlProp.p1Consent : urlProp.noP1Consent + if (!url) { + logWarn('Missing matching consent URL when gdpr=' + hasPurpose); + } + return url; } function getConsentData(bidRequests) { @@ -457,8 +460,9 @@ export function PrebidServer() { if (Array.isArray(_s2sConfigs)) { if (s2sBidRequest.s2sConfig && s2sBidRequest.s2sConfig.syncEndpoint && getMatchingConsentUrl(s2sBidRequest.s2sConfig.syncEndpoint, gdprConsent)) { + const s2sAliases = (s2sBidRequest.s2sConfig.extPrebid && s2sBidRequest.s2sConfig.extPrebid.aliases) ?? {}; let syncBidders = s2sBidRequest.s2sConfig.bidders - .map(bidder => adapterManager.aliasRegistry[bidder] || bidder) + .map(bidder => adapterManager.aliasRegistry[bidder] || s2sAliases[bidder] || bidder) .filter((bidder, index, array) => (array.indexOf(bidder) === index)); queueSync(syncBidders, gdprConsent, uspConsent, gppConsent, s2sBidRequest.s2sConfig); @@ -469,7 +473,8 @@ export function PrebidServer() { if (isValid) { bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_DONE, bidderRequest)); } - if (shouldEmitNonbids(s2sBidRequest.s2sConfig, response)) { + const { seatNonBidData, atagData } = getAnalyticsFlags(s2sBidRequest.s2sConfig, response) + if (seatNonBidData) { events.emit(EVENTS.SEAT_NON_BID, { seatnonbid: response.ext.seatnonbid, auctionId: bidRequests[0].auctionId, @@ -478,11 +483,28 @@ export function PrebidServer() { adapterMetrics }); } + // pbs analytics event + if (seatNonBidData || atagData) { + const data = { + seatnonbid: seatNonBidData, + atag: atagData, + auctionId: bidRequests[0].auctionId, + requestedBidders, + response, + adapterMetrics + } + events.emit(EVENTS.PBS_ANALYTICS, data); + } done(false); doClientSideSyncs(requestedBidders, gdprConsent, uspConsent, gppConsent); }, onError(msg, error) { - logError(`Prebid server call failed: '${msg}'`, error); + const {p1Consent = '', noP1Consent = ''} = s2sBidRequest?.s2sConfig?.endpoint || {}; + if (p1Consent === noP1Consent) { + logError(`Prebid server call failed: '${msg}'. Endpoint: "${p1Consent}"}`, error); + } else { + logError(`Prebid server call failed: '${msg}'. Endpoints: p1Consent "${p1Consent}", noP1Consent "${noP1Consent}"}`, error); + } bidRequests.forEach(bidderRequest => events.emit(EVENTS.BIDDER_ERROR, { error, bidderRequest })); done(error.timedOut); }, @@ -504,7 +526,9 @@ export function PrebidServer() { } }, onFledge: (params) => { - addComponentAuction({auctionId: bidRequests[0].auctionId, ...params}, params.config); + config.runWithBidder(params.bidder, () => { + addPaapiConfig({auctionId: bidRequests[0].auctionId, ...params}, {config: params.config}); + }) } }) } @@ -540,10 +564,11 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques .reduce(flatten, []) .filter(uniques); - const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders, eidPermissions)); + const request = s2sBidRequest.metrics.measureTime('buildRequests', () => buildPBSRequest(s2sBidRequest, bidRequests, adUnits, requestedBidders)); const requestJson = request && JSON.stringify(request); logInfo('BidRequest: ' + requestJson); const endpointUrl = getMatchingConsentUrl(s2sBidRequest.s2sConfig.endpoint, gdprConsent); + const customHeaders = deepAccess(s2sBidRequest, 's2sConfig.customHeaders', {}); if (request && requestJson && endpointUrl) { const networkDone = s2sBidRequest.metrics.startTiming('net'); ajax( @@ -554,10 +579,10 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques let result; try { result = JSON.parse(response); - const {bids, fledgeAuctionConfigs} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); + const {bids, paapi} = s2sBidRequest.metrics.measureTime('interpretResponse', () => interpretPBSResponse(result, request)); bids.forEach(onBid); - if (fledgeAuctionConfigs) { - fledgeAuctionConfigs.forEach(onFledge); + if (paapi) { + paapi.forEach(onFledge); } } catch (error) { logError(error); @@ -578,7 +603,8 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques { contentType: 'text/plain', withCredentials: true, - browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)) + browsingTopics: isActivityAllowed(ACTIVITY_TRANSMIT_UFPD, s2sActivityParams(s2sBidRequest.s2sConfig)), + customHeaders } ); } else { @@ -586,18 +612,18 @@ export const processPBSRequest = hook('sync', function (s2sBidRequest, bidReques } }, 'processPBSRequest'); -function shouldEmitNonbids(s2sConfig, response) { - return s2sConfig?.extPrebid?.returnallbidstatus && response?.ext?.seatnonbid; +function getAnalyticsFlags(s2sConfig, response) { + return { + atagData: getAtagData(response), + seatNonBidData: getNonBidData(s2sConfig, response) + } +} +function getNonBidData(s2sConfig, response) { + return s2sConfig?.extPrebid?.returnallbidstatus ? response?.ext?.seatnonbid : undefined; } -/** - * 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; +function getAtagData(response) { + return response?.ext?.prebid?.analytics?.tags; } -getPrebidInternal().setEidPermissions = setEidPermissions; adapterManager.registerBidAdapter(new PrebidServer(), 'prebidServer'); diff --git a/modules/prebidServerBidAdapter/ortbConverter.js b/modules/prebidServerBidAdapter/ortbConverter.js index e0f038767c2..67cc5a372cf 100644 --- a/modules/prebidServerBidAdapter/ortbConverter.js +++ b/modules/prebidServerBidAdapter/ortbConverter.js @@ -1,17 +1,7 @@ import {ortbConverter} from '../../libraries/ortbConverter/converter.js'; -import { - deepAccess, - deepSetValue, - getBidRequest, - getDefinedParams, - isArray, - logError, - logWarn, - mergeDeep, - timestamp -} from '../../src/utils.js'; +import {deepAccess, deepSetValue, getBidRequest, logError, logWarn, mergeDeep, timestamp} from '../../src/utils.js'; import {config} from '../../src/config.js'; -import { STATUS, S2S } from '../../src/constants.js'; +import {S2S, STATUS} from '../../src/constants.js'; import {createBid} from '../../src/bidfactory.js'; import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; import {setImpBidParams} from '../../libraries/pbsExtensions/processors/params.js'; @@ -25,6 +15,7 @@ import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_TRANSMIT_TID} from '../../src/activities/activities.js'; import {currencyCompare} from '../../libraries/currencyUtils/currency.js'; import {minimum} from '../../src/utils/reducers.js'; +import {s2sDefaultConfig} from './index.js'; const DEFAULT_S2S_TTL = 60; const DEFAULT_S2S_CURRENCY = 'USD'; @@ -46,7 +37,7 @@ const PBS_CONVERTER = ortbConverter({ } }); if (Object.values(SUPPORTED_MEDIA_TYPES).some(mtype => imp[mtype])) { - imp.secure = context.s2sBidRequest.s2sConfig.secure; + imp.secure = proxyBidRequest.ortb2Imp?.secure ?? 1; return imp; } }, @@ -54,10 +45,11 @@ const PBS_CONVERTER = ortbConverter({ if (!imps.length) { logError('Request to Prebid Server rejected due to invalid media type(s) in adUnit.'); } else { - let {s2sBidRequest, requestedBidders, eidPermissions} = context; + let {s2sBidRequest} = context; const request = buildRequest(imps, proxyBidderRequest, context); - request.tmax = s2sBidRequest.s2sConfig.timeout; + request.tmax = s2sBidRequest.s2sConfig.timeout ?? Math.min(s2sBidRequest.requestBidsTimeout * 0.75, s2sBidRequest.s2sConfig.maxTimeout ?? s2sDefaultConfig.maxTimeout); + request.ext.tmaxmax = request.ext.tmaxmax || s2sBidRequest.requestBidsTimeout; [request.app, request.dooh, request.site].forEach(section => { if (section && !section.publisher?.id) { @@ -65,16 +57,6 @@ const PBS_CONVERTER = ortbConverter({ } }) - if (isArray(eidPermissions) && eidPermissions.length > 0) { - if (requestedBidders && isArray(requestedBidders)) { - eidPermissions = eidPermissions.map(p => ({ - ...p, - bidders: p.bidders.filter(bidder => requestedBidders.includes(bidder)) - })) - } - deepSetValue(request, 'ext.prebid.data.eidpermissions', eidPermissions); - } - if (!context.transmitTids) { deepSetValue(request, 'ext.prebid.createtids', false); } @@ -120,7 +102,10 @@ const PBS_CONVERTER = ortbConverter({ transactionId: context.adUnit.transactionId, adUnitId: context.adUnit.adUnitId, auctionId: context.bidderRequest.auctionId, - }), bidResponse), + }), bidResponse, { + deferRendering: !!context.adUnit.deferBilling, + deferBilling: !!context.adUnit.deferBilling + }), adUnit: context.adUnit.code }; }, @@ -197,9 +182,7 @@ const PBS_CONVERTER = ortbConverter({ context.actualBidderRequests.forEach(req => orig(ortbRequest, req, context)); }, sourceExtSchain(orig, ortbRequest, proxyBidderRequest, context) { - // pass schains in ext.prebid.schains, with the most commonly used one in source.ext.schain - let mainChain; - + // pass schains in ext.prebid.schains let chains = (deepAccess(ortbRequest, 'ext.prebid.schains') || []); const chainBidders = new Set(chains.flatMap((item) => item.bidders)); @@ -218,17 +201,10 @@ const PBS_CONVERTER = ortbConverter({ chains[key] = {bidders: new Set(), schain}; } bidders.forEach((bidder) => chains[key].bidders.add(bidder)); - if (mainChain == null || chains[key].bidders.size > mainChain.bidders.size) { - mainChain = chains[key] - } return chains; }, {}) ).map(({bidders, schain}) => ({bidders: Array.from(bidders), schain})); - if (mainChain != null) { - deepSetValue(ortbRequest, 'source.ext.schain', mainChain.schain); - } - if (chains.length) { deepSetValue(ortbRequest, 'ext.prebid.schains', chains); } @@ -239,27 +215,28 @@ const PBS_CONVERTER = ortbConverter({ // override to process each request context.actualBidderRequests.forEach(req => orig(response, ortbResponse, {...context, bidderRequest: req, bidRequests: req.bids})); }, - fledgeAuctionConfigs(orig, response, ortbResponse, context) { + paapiConfigs(orig, response, ortbResponse, context) { const configs = Object.values(context.impContext) - .flatMap((impCtx) => (impCtx.fledgeConfigs || []).map(cfg => { + .flatMap((impCtx) => (impCtx.paapiConfigs || []).map(cfg => { const bidderReq = impCtx.actualBidderRequests.find(br => br.bidderCode === cfg.bidder); const bidReq = impCtx.actualBidRequests.get(cfg.bidder); return { adUnitCode: impCtx.adUnit.code, ortb2: bidderReq?.ortb2, ortb2Imp: bidReq?.ortb2Imp, + bidder: cfg.bidder, config: cfg.config }; })); if (configs.length > 0) { - response.fledgeAuctionConfigs = configs; + response.paapi = configs; } } } }, }); -export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requestedBidders, eidPermissions) { +export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requestedBidders) { const requestTimestamp = timestamp(); const impIds = new Set(); const proxyBidRequests = []; @@ -301,14 +278,15 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste proxyBidRequests.push({ ...adUnit, adUnitCode: adUnit.code, - ...getDefinedParams(actualBidRequests.values().next().value || {}, ['userId', 'userIdAsEids', 'schain']), pbsData: {impId, actualBidRequests, adUnit}, }); }); const proxyBidderRequest = { ...Object.fromEntries(Object.entries(bidderRequests[0]).filter(([k]) => !BIDDER_SPECIFIC_REQUEST_PROPS.has(k))), - fledgeEnabled: bidderRequests.some(req => req.fledgeEnabled) + paapi: { + enabled: bidderRequests.some(br => br.paapi?.enabled) + } } return PBS_CONVERTER.toORTB({ @@ -321,7 +299,6 @@ export function buildPBSRequest(s2sBidRequest, bidderRequests, adUnits, requeste s2sBidRequest, requestedBidders, actualBidderRequests: bidderRequests, - eidPermissions, nativeRequest: s2sBidRequest.s2sConfig.ortbNative, getRedactor, transmitTids: isActivityAllowed(ACTIVITY_TRANSMIT_TID, s2sParams), diff --git a/modules/prebidmanagerAnalyticsAdapter.md b/modules/prebidmanagerAnalyticsAdapter.md deleted file mode 100644 index 030e79b406f..00000000000 --- a/modules/prebidmanagerAnalyticsAdapter.md +++ /dev/null @@ -1,9 +0,0 @@ -# Overview - -Module Name: Prebid Manager Analytics Adapter -Module Type: Analytics Adapter -Maintainer: admin@prebidmanager.com - -# Description - -Analytics adapter for Prebid Manager. Contact admin@prebidmanager.com for information. diff --git a/modules/precisoBidAdapter.js b/modules/precisoBidAdapter.js index 9125f6f3911..8c244dca040 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,212 +1,66 @@ -import { logMessage, isFn, deepAccess, logInfo } from '../src/utils.js'; +import { logInfo } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - -const BIDDER_CODE = 'preciso'; -const AD_URL = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; -const URL_SYNC = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; -const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE, VIDEO]; +import { BANNER, NATIVE } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { buildBidResponse, buildRequests, onBidWon } from '../libraries/precisoUtils/bidUtils.js'; +import { buildUserSyncs } from '../libraries/precisoUtils/bidUtilsCommon.js'; + +const BIDDER__CODE = 'preciso'; +export const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); +const SUPPORTED_MEDIA_TYPES = [BANNER, NATIVE]; const GVLID = 874; -let userId = 'NA'; +let precisoId = 'NA'; +let sharedId = 'NA'; + +const endpoint = 'https://ssp-bidder.mndtrk.com/bid_request/openrtb'; +let syncEndpoint = 'https://ck.2trk.info/rtb/user/usersync.aspx?'; export const spec = { - code: BIDDER_CODE, + code: BIDDER__CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, gvlid: GVLID, isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && !isNaN(bid.params.publisherId) && bid.params.host == 'prebid'); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - // userId = validBidRequests[0].userId.pubcid; - let winTop = window; - let location; - var offset = new Date().getTimezoneOffset(); - logInfo('timezone ' + offset); - var city = Intl.DateTimeFormat().resolvedOptions().timeZone; - logInfo('location test' + city) - - const countryCode = getCountryCodeByTimezone(city); - logInfo(`The country code for ${city} is ${countryCode}`); - - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - - let request = { - id: validBidRequests[0].bidderRequestId, - - imp: validBidRequests.map(request => { - const { bidId, sizes, mediaType, ortb2 } = request - const item = { - id: bidId, - region: request.params.region, - traffic: mediaType, - bidFloor: getBidFloor(request), - ortb2: ortb2 - - } - - if (request.mediaTypes.banner) { - item.banner = { - format: (request.mediaTypes.banner.sizes || sizes).map(size => { - return { w: size[0], h: size[1] } - }), - } - } - - if (request.schain) { - item.schain = request.schain; - } - - if (request.floorData) { - item.bidFloor = request.floorData.floorMin; - } - return item - }), - auctionId: validBidRequests[0].auctionId, - 'deviceWidth': winTop.screen.width, - 'deviceHeight': winTop.screen.height, - 'language': (navigator && navigator.language) ? navigator.language : '', - geo: navigator.geolocation.getCurrentPosition(position => { - const { latitude, longitude } = position.coords; - return { - latitude: latitude, - longitude: longitude - } - // Show a map centered at latitude / longitude. - }) || { utcoffset: new Date().getTimezoneOffset() }, - city: city, - 'host': location.host, - 'page': location.pathname, - 'coppa': config.getConfig('coppa') === true ? 1 : 0 - // userId: validBidRequests[0].userId - }; - - 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 - } - if (bidderRequest.gppConsent) { - request.gpp = bidderRequest.gppConsent; + sharedId = storage.getDataFromLocalStorage('_sharedid') || storage.getCookie('_sharedid'); + let precisoBid = true; + const preCall = 'https://ssp-usersync.mndtrk.com/getUUID?sharedId=' + sharedId; + precisoId = storage.getDataFromLocalStorage('_pre|id'); + if (Object.is(precisoId, 'NA') || Object.is(precisoId, null) || Object.is(precisoId, undefined)) { + if (!bid.precisoBid) { + precisoBid = false; + getapi(preCall); } } - return { - method: 'POST', - url: AD_URL, - data: request, - - }; + return Boolean(bid.bidId && bid.params && bid.params.publisherId && precisoBid); }, - - interpretResponse: function (serverResponse) { - const response = serverResponse.body - - const bids = [] - - response.seatbid.forEach(seat => { - seat.bid.forEach(bid => { - bids.push({ - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.crid, - ad: bid.adm, - currency: 'USD', - netRevenue: true, - ttl: 300, - meta: { - advertiserDomains: bid.adomain || [], - }, - }) - }) - }) - - return bids - }, - - getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { - let syncs = []; - let { gdprApplies, consentString = '' } = gdprConsent; - - if (serverResponses.length > 0) { - logInfo('preciso bidadapter getusersync serverResponses:' + serverResponses.toString); - } - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `${URL_SYNC}id=${userId}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=4` - }); - } else { - syncs.push({ - type: 'image', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=2` - }); - } - - return syncs + buildRequests: buildRequests(endpoint), + interpretResponse: buildBidResponse, + onBidWon, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + syncEndpoint = syncEndpoint + 'id=' + sharedId; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint); } - }; -function getCountryCodeByTimezone(city) { +registerBidder(spec); + +async function getapi(url) { try { - const now = new Date(); - const options = { - timeZone: city, - timeZoneName: 'long', - }; - const [timeZoneName] = new Intl.DateTimeFormat('en-US', options) - .formatToParts(now) - .filter((part) => part.type === 'timeZoneName'); + const response = await fetch(url); + var data = await response.json(); + + const dataMap = new Map(Object.entries(data)); + const uuidValue = dataMap.get('UUID'); - if (timeZoneName) { - // Extract the country code from the timezone name - const parts = timeZoneName.value.split('-'); - if (parts.length >= 2) { - return parts[1]; + if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { + if (storage.localStorageIsEnabled()) { + storage.setDataInLocalStorage('_pre|id', uuidValue); } } + return data; } catch (error) { - // Handle errors, such as an invalid timezone city - logInfo(error); + logInfo('Error in preciso precall' + error); } - - // Handle the case where the city is not found or an error occurred - return 'Unknown'; } - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -registerBidder(spec); diff --git a/modules/precisoBidAdapter.md b/modules/precisoBidAdapter.md index b1fb0d062da..97521f195d8 100644 --- a/modules/precisoBidAdapter.md +++ b/modules/precisoBidAdapter.md @@ -14,9 +14,9 @@ Module that connects to preciso' demand sources | Name | Scope | Description | Example | | :------------ | :------- | :------------------------ | :------------------- | -| `region` | required (for prebid.js) | region | "prebid-eu" | -| `publisherId` | required (for prebid-server) | partner ID | "1901" | -| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native or video | "banner" | +| `region` | optional (for prebid.js) | 3 letter country code | "USA" | +| `publisherId` | required (for prebid-server) | partner ID provided by preciso | PreTest_0001 | +| `traffic` | optional (for prebid.js) | Configures the mediaType that should be used. Values can be banner, native | "banner" | # Test Parameters ``` @@ -25,7 +25,35 @@ Module that connects to preciso' demand sources { code: 'placementId_0', mediaTypes: { - native: {} + native: { + ortb: { + assets: [ + { + id: 3, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + }, + { + id: 1, + required: 1, + title: { + len: 800 + } + }, + { + id: 4, + required: 0, + data: { + type: 1 + } + } + ] + } + } }, bids: [ { @@ -33,7 +61,7 @@ Module that connects to preciso' demand sources params: { host: 'prebid', publisherId: '0', - region: 'prebid-eu', + region: 'USA', traffic: 'native' } } @@ -53,32 +81,11 @@ Module that connects to preciso' demand sources params: { host: 'prebid', publisherId: '0', - region: 'prebid-eu', + region: 'USA', traffic: 'banner' } } ] - }, - // 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: 'preciso', - params: { - host: 'prebid', - publisherId: '0', - region: 'prebid-eu', - traffic: 'video' - } - } - ] } ]; ``` \ No newline at end of file diff --git a/modules/priceFloors.js b/modules/priceFloors.js index 5df8f938c3d..d14a82af360 100644 --- a/modules/priceFloors.js +++ b/modules/priceFloors.js @@ -3,7 +3,6 @@ import { deepAccess, deepClone, deepSetValue, - generateUUID, getParameterByName, isNumber, logError, @@ -13,7 +12,8 @@ import { parseGPTSingleSizeArray, parseUrl, pick, - deepEqual + deepEqual, + generateUUID } from '../src/utils.js'; import {getGlobal} from '../src/prebidGlobal.js'; import {config} from '../src/config.js'; @@ -30,6 +30,7 @@ import {timedAuctionHook, timedBidResponseHook} from '../src/utils/perfMetrics.j import {adjustCpm} from '../src/utils/cpm.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; import {convertCurrency} from '../libraries/currencyUtils/currency.js'; +import { timeoutQueue } from '../libraries/timeoutQueue/timeoutQueue.js'; export const FLOOR_SKIPPED_REASON = { NOT_FOUND: 'not_found', @@ -72,7 +73,7 @@ let _floorsConfig = {}; /** * @summary If a auction is to be delayed by an ongoing fetch we hold it here until it can be resumed */ -let _delayedAuctions = []; +const _delayedAuctions = timeoutQueue(); /** * @summary Each auction can have differing floors data depending on execution time or per adunit setup @@ -270,6 +271,10 @@ export function getFloor(requestParams = {currency: 'USD', mediaType: '*', size: } } + if (floorInfo.floorRuleValue === null) { + return null; + } + if (floorInfo.matchingFloor) { return { floor: roundUp(floorInfo.matchingFloor, 4), @@ -436,17 +441,11 @@ export function createFloorsDataForAuction(adUnits, auctionId) { * @summary This is the function which will be called to exit our module and continue the auction. */ export function continueAuction(hookConfig) { - // only run if hasExited if (!hookConfig.hasExited) { - // if this current auction is still fetching, remove it from the _delayedAuctions - _delayedAuctions = _delayedAuctions.filter(auctionConfig => auctionConfig.timer !== hookConfig.timer); - // We need to know the auctionId at this time. So we will use the passed in one or generate and set it ourselves hookConfig.reqBidsConfigObj.auctionId = hookConfig.reqBidsConfigObj.auctionId || generateUUID(); - // now we do what we need to with adUnits and save the data object to be used for getFloor and enforcement calls _floorDataForAuction[hookConfig.reqBidsConfigObj.auctionId] = createFloorsDataForAuction(hookConfig.reqBidsConfigObj.adUnits || getGlobal().adUnits, hookConfig.reqBidsConfigObj.auctionId); - hookConfig.nextFn.apply(hookConfig.context, [hookConfig.reqBidsConfigObj]); hookConfig.hasExited = true; } @@ -467,7 +466,7 @@ function isValidRule(key, floor, numFields, delimiter) { if (typeof key !== 'string' || key.split(delimiter).length !== numFields) { return false; } - return typeof floor === 'number'; + return typeof floor === 'number' || floor === null; } function validateRules(floorsData, numFields, delimiter) { @@ -577,36 +576,22 @@ export const requestBidsHook = timedAuctionHook('priceFloors', function requestB reqBidsConfigObj, context: this, nextFn: fn, - haveExited: false, + hasExited: false, timer: null }; // If auction delay > 0 AND we are fetching -> Then wait until it finishes if (_floorsConfig.auctionDelay > 0 && fetching) { - hookConfig.timer = setTimeout(() => { + _delayedAuctions.submit(_floorsConfig.auctionDelay, () => continueAuction(hookConfig), () => { logWarn(`${MODULE_NAME}: Fetch attempt did not return in time for auction`); _floorsConfig.fetchStatus = 'timeout'; continueAuction(hookConfig); - }, _floorsConfig.auctionDelay); - _delayedAuctions.push(hookConfig); + }); } else { continueAuction(hookConfig); } }); -/** - * @summary If an auction was queued to be delayed (waiting for a fetch) then this function will resume - * those delayed auctions when delay is hit or success return or fail return - */ -function resumeDelayedAuctions() { - _delayedAuctions.forEach(auctionConfig => { - // clear the timeout - clearTimeout(auctionConfig.timer); - continueAuction(auctionConfig); - }); - _delayedAuctions = []; -} - /** * This function handles the ajax response which comes from the user set URL to fetch floors data from * @param {object} fetchResponse The floors data response which came back from the url configured in config.floors @@ -631,7 +616,7 @@ export function handleFetchResponse(fetchResponse) { } // if any auctions are waiting for fetch to finish, we need to continue them! - resumeDelayedAuctions(); + _delayedAuctions.resume(); } function handleFetchError(status) { @@ -640,7 +625,7 @@ function handleFetchError(status) { logError(`${MODULE_NAME}: Fetch errored with: `, status); // if any auctions are waiting for fetch to finish, we need to continue them! - resumeDelayedAuctions(); + _delayedAuctions.resume(); } /** @@ -827,7 +812,7 @@ export function setOrtbImpBidFloor(imp, bidRequest, context) { currency: context.currency || config.getConfig('currency.adServerCurrency') || 'USD', mediaType: context.mediaType || '*', size: '*' - })); + }) || {}); } catch (e) { logWarn('Cannot compute floor for bid', bidRequest); return; diff --git a/modules/prismaBidAdapter.js b/modules/prismaBidAdapter.js index b42c4b8af3f..9f7d37dcebe 100644 --- a/modules/prismaBidAdapter.js +++ b/modules/prismaBidAdapter.js @@ -3,6 +3,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; +import {getConnectionType} from '../libraries/connectionInfo/connectionUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -20,33 +21,6 @@ const METRICS_TRACKER_URL = 'https://prisma.nexx360.io/track-imp'; const GVLID = 965; -function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; - if (!connection) { - return 0; - } - switch (connection.type) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'cellular': - switch (connection.effectiveType) { - case 'slow-2g': - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - default: - return 3; - } - default: - return 0; - } -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, diff --git a/modules/programmaticaBidAdapter.js b/modules/programmaticaBidAdapter.js index 7d52e305189..aeca74120d6 100644 --- a/modules/programmaticaBidAdapter.js +++ b/modules/programmaticaBidAdapter.js @@ -1,6 +1,6 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { hasPurpose1Consent } from '../src/utils/gpdr.js'; +import { hasPurpose1Consent } from '../src/utils/gdpr.js'; import { deepAccess, parseSizesInput, isArray } from '../src/utils.js'; const BIDDER_CODE = 'programmatica'; diff --git a/modules/pstudioBidAdapter.js b/modules/pstudioBidAdapter.js index 77a11ac58c6..cc9310e174b 100644 --- a/modules/pstudioBidAdapter.js +++ b/modules/pstudioBidAdapter.js @@ -6,16 +6,14 @@ import { isNumber, generateUUID, isEmpty, - isFn, - isPlainObject, } from '../src/utils.js'; import { getStorageManager } from '../src/storageManager.js'; const BIDDER_CODE = 'pstudio'; const ENDPOINT = 'https://exchange.pstudio.tadex.id/prebid-bid' const TIME_TO_LIVE = 300; -// in case that the publisher limits number of user syncs, thisse syncs will be discarded from the end of the list -// so more improtant syncing calls should be at the start of the list +// in case that the publisher limits number of user syncs, these syncs will be discarded from the end of the list +// so more important syncing calls should be at the start of the list const USER_SYNCS = [ // PARTNER_UID is a partner user id { @@ -40,6 +38,7 @@ const VIDEO_PARAMS = [ 'protocols', 'startdelay', 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', @@ -58,7 +57,7 @@ export const spec = { isBidRequestValid: function (bid) { const params = bid.params || {}; - return !!params.pubid && !!params.floorPrice && isVideoRequestValid(bid); + return !!params.pubid && !!params.adtagid && isVideoRequestValid(bid); }, buildRequests: function (validBidRequests, bidderRequest) { @@ -144,7 +143,7 @@ function buildRequestData(bid, bidderRequest) { function buildBaseObject(bid, bidderRequest) { const firstPartyData = prepareFirstPartyData(bidderRequest.ortb2); - const { pubid, bcat, badv, bapp } = bid.params; + const { pubid, adtagid, bcat, badv, bapp } = bid.params; const { userId } = bid; const uid2Token = userId?.uid2?.id; @@ -167,8 +166,7 @@ function buildBaseObject(bid, bidderRequest) { return { id: bid.bidId, pubid, - floor_price: getBidFloor(bid), - adtagid: bid.adUnitCode, + adtagid: adtagid, ...(bcat && { bcat }), ...(badv && { badv }), ...(bapp && { bapp }), @@ -416,7 +414,7 @@ function validateSizes(sizes) { ); } -function getBidFloor(bid) { +/* function getBidFloor(bid) { if (!isFn(bid.getFloor)) { return bid.params.floorPrice ? bid.params.floorPrice : null; } @@ -430,6 +428,6 @@ function getBidFloor(bid) { return floor.floor; } return null; -} +} */ registerBidder(spec); diff --git a/modules/pstudioBidAdapter.md b/modules/pstudioBidAdapter.md index 0c8c6927f43..a4b3e098cfc 100644 --- a/modules/pstudioBidAdapter.md +++ b/modules/pstudioBidAdapter.md @@ -31,7 +31,8 @@ var adUnits = [ params: { // id of test publisher pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + // id of test adtag id + adtagid: '6f3173b9-5623-4a4f-8c62-2b1d24ceb4e6', }, }, ], @@ -53,7 +54,8 @@ var adUnits = [ params: { // id of test publisher pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + // id of test adtag id + adtagid: '097c601f-ad09-495b-b70b-d9cf6f1edbc1', }, }, ], @@ -79,7 +81,7 @@ var adUnits = [ bidder: 'pstudio', params: { pubid: '22430f9d-9610-432c-aabe-6134256f11af', // required - floorPrice: 1.15, // required + adtagid: 'b9be4c35-3c12-4fa9-96ba-34b90276208c', // required bcat: ['IAB1-1', 'IAB1-3'], // optional badv: ['nike.com'], // optional bapp: ['com.foo.mygame'], // optional @@ -121,7 +123,7 @@ var videoAdUnits = [ bidder: 'pstudio', params: { pubid: '22430f9d-9610-432c-aabe-6134256f11af', - floorPrice: 1.25, + adtagid: '46e348cf-b79d-43e5-81bc-5954cdf15d7e', badv: ['adidas.com'], }, }, diff --git a/modules/pubCircleBidAdapter.js b/modules/pubCircleBidAdapter.js index 54224fd0403..c63b80b819c 100644 --- a/modules/pubCircleBidAdapter.js +++ b/modules/pubCircleBidAdapter.js @@ -1,231 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'pubcircle'; const AD_URL = 'https://ml.pubcircle.ai/pbjs'; const SYNC_URL = 'https://cs.pubcircle.ai'; -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 = { - bidId, - schain, - bidfloor - }; - - placement.placementId = placementId; - placement.type = 'publisher'; - - if (bid.userId) { - getUserId(placement.eids, bid.userId.uid2 && bid.userId.uid2.id, 'uidapi.com'); - getUserId(placement.eids, bid.userId.lotamePanoramaId, 'lotame.com'); - getUserId(placement.eids, bid.userId.idx, 'idx.lat'); - getUserId(placement.eids, bid.userId.idl_env, 'liveramp.com'); - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - -function getUserId(eids, id, source, uidExt) { - if (id) { - var uid = { id }; - if (uidExt) { - uid.ext = uidExt; - } - eids.push({ - source, - uids: [ uid ] - }); - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - 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 = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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 - }]; - }, - - onBidViewable: function (bid) { - // to do : we need to implement js tag to fire pixel with viewability counter - } + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/pubgeniusBidAdapter.js b/modules/pubgeniusBidAdapter.js index d92a9352cee..19260e65e60 100644 --- a/modules/pubgeniusBidAdapter.js +++ b/modules/pubgeniusBidAdapter.js @@ -155,7 +155,7 @@ function buildVideoParams(videoMediaType, videoParams) { 'maxduration', 'protocols', 'startdelay', - 'placement', + 'plcmt', 'skip', 'skipafter', 'minbitrate', @@ -166,17 +166,6 @@ function buildVideoParams(videoMediaType, videoParams) { '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]; @@ -205,7 +194,7 @@ function buildImp(bid) { mediaType: bid.mediaTypes.banner ? 'banner' : 'video', size: '*', currency: 'USD', - }); + }) || {}; if (floor) { imp.bidfloor = floor; @@ -301,8 +290,7 @@ function isValidBanner(banner) { function isValidVideo(videoMediaType, videoParams) { const params = buildVideoParams(videoMediaType, videoParams); - return !!(params.placement && - isValidSize([params.w, params.h]) && + return !!(isValidSize([params.w, params.h]) && params.mimes && params.mimes.length && isArrayOfNums(params.protocols) && params.protocols.length); } diff --git a/modules/publirBidAdapter.js b/modules/publirBidAdapter.js index 432e123458c..2cf55aa86cb 100644 --- a/modules/publirBidAdapter.js +++ b/modules/publirBidAdapter.js @@ -2,32 +2,26 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, timestamp, triggerPixel, - getDNT, - getBidIdParameter } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; import { ajax } from '../src/ajax.js'; +import { + generateBidsParams, + generateGeneralParams, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'publir'; const ADAPTER_VERSION = '1.0.0'; const TTL = 360; const CURRENCY = 'USD'; -const DEFAULT_SELLER_ENDPOINT = 'https://prebid.publir.com/publirPrebidEndPoint'; +const BASE_URL = 'https://prebid.publir.com/publirPrebidEndPoint'; const DEFAULT_IMPS_ENDPOINT = 'https://prebidimpst.publir.com/publirPrebidImpressionTracker'; -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} export const storage = getStorageManager({ bidderCode: BIDDER_CODE }); export const spec = { @@ -40,25 +34,26 @@ export const spec = { logWarn('pubId is a mandatory param for Publir adapter'); return false; } - return true; }, buildRequests: function (validBidRequests, bidderRequest) { - const reqObj = {}; + const combinedRequestsObject = {}; + const generalObject = validBidRequests[0]; - reqObj.params = generatePubGeneralsParams(generalObject, bidderRequest); - reqObj.bids = generatePubBidParams(validBidRequests, bidderRequest); - reqObj.bids.timestamp = timestamp(); + combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest, ADAPTER_VERSION); + combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); + combinedRequestsObject.bids.timestamp = timestamp(); + let options = { withCredentials: false }; return { method: 'POST', - url: DEFAULT_SELLER_ENDPOINT, - data: reqObj, + url: BASE_URL, + data: combinedRequestsObject, options - } + }; }, interpretResponse: function ({ body }) { const bidResponses = []; @@ -109,20 +104,20 @@ export const spec = { const syncs = []; for (const response of serverResponses) { if (response.body && response.body.params) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel - } - }) - syncs.push(...pixels) + }; + }); + syncs.push(...pixels); } } } @@ -141,251 +136,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(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 !isEmpty(val) ? encodeURIComponent(val) : ''; -} - -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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * get device type - * @param ua {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'; -} - -function generatePubBidParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const { params } = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - transactionId: bid.ortb2Imp?.ext?.tid, - coppa: 0 - }; - - const pubId = params.pubId; - if (pubId) { - bidObject.pubId = pubId; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -function getLocalStorage(cookieObjName) { - if (storage.localStorageIsEnabled()) { - const lstData = storage.getDataFromLocalStorage(cookieObjName); - return lstData; - } - return ''; -} - -/** - * Generate params that are common between all bids - * @param generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generatePubGeneralsParams(generalObject, bidderRequest) { - const domain = bidderRequest.refererInfo; - const { syncEnabled, filterSettings } = config.getConfig('userSync') || {}; - const { bidderCode } = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: bidderRequest.auctionStart, - publisher_id: generalBidParams.pubId, - publisher_name: domain, - site_domain: domain, - dnt: getDNT() ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout, - user_cookie: getLocalStorage('_publir_prebid_creative') - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/pubmaticAnalyticsAdapter.js b/modules/pubmaticAnalyticsAdapter.js index 9e1fa49fef2..2fec213a612 100755 --- a/modules/pubmaticAnalyticsAdapter.js +++ b/modules/pubmaticAnalyticsAdapter.js @@ -281,7 +281,7 @@ function isOWPubmaticBid(adapterName) { }) } -function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { +function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid, e) { highestBid = (highestBid && highestBid.length > 0) ? highestBid[0] : null; return Object.keys(adUnit.bids).reduce(function(partnerBids, bidId) { adUnit.bids[bidId].forEach(function(bid) { @@ -290,6 +290,16 @@ function gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestBid) { return; } const pg = window.parseFloat(Number(bid.bidResponse?.adserverTargeting?.hb_pb || bid.bidResponse?.adserverTargeting?.pwtpb).toFixed(BID_PRECISION)); + + const prebidBidsReceived = e?.bidsReceived; + if (isArray(prebidBidsReceived) && prebidBidsReceived.length > 0) { + prebidBidsReceived.forEach(function(iBid) { + if (iBid.adId === bid.adId) { + bid.bidderCode = iBid.bidderCode; + } + }); + } + partnerBids.push({ 'pn': adapterName, 'bc': bid.bidderCode || bid.bidder, @@ -391,7 +401,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { outputObj['pdvid'] = '' + profileVersionId; outputObj['dvc'] = {'plt': getDevicePlatform()}; outputObj['tgid'] = getTgId(); - outputObj['pbv'] = getGlobal()?.version || '-1'; + outputObj['pbv'] = '$prebid.version$' || '-1'; if (floorData && floorFetchStatus) { outputObj['fmv'] = floorData.floorRequestData ? floorData.floorRequestData.modelVersion || undefined : undefined; @@ -407,7 +417,7 @@ function executeBidsLoggerCall(e, highestCpmBids) { 'au': origAdUnit.owAdUnitId || getGptSlotInfoForAdUnitCode(adUnitId)?.gptSlot || adUnitId, 'mt': getAdUnitAdFormats(origAdUnit), 'sz': getSizesForAdUnit(adUnit, adUnitId), - 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId)), + 'ps': gatherPartnerBidsForAdUnitForLogger(adUnit, adUnitId, highestCpmBids.filter(bid => bid.adUnitCode === adUnitId), e), 'fskp': floorData && floorFetchStatus ? (floorData.floorRequestData ? (floorData.floorRequestData.skipped == false ? 0 : 1) : undefined) : undefined, 'sid': generateUUID() }; @@ -489,7 +499,7 @@ function executeBidWonLoggerCall(auctionId, adUnitId) { pixelURL += '&kgpv=' + enc(getValueForKgpv(winningBid, adUnitId)); pixelURL += '&origbidid=' + enc(winningBid?.bidResponse?.partnerImpId || winningBid?.bidResponse?.prebidBidId || winningBid.bidId); pixelURL += '&di=' + enc(winningBid?.bidResponse?.dealId || OPEN_AUCTION_DEAL_ID); - pixelURL += '&pb=' + enc(pg); + pg && (pixelURL += '&pb=' + enc(pg)); pixelURL += '&plt=' + enc(getDevicePlatform()); pixelURL += '&psz=' + enc((winningBid?.bidResponse?.dimensions?.width || '0') + 'x' + @@ -559,7 +569,8 @@ function bidResponseHandler(args) { logWarn(LOG_PRE_FIX + 'Got null requestId in bidResponseHandler'); return; } - let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId][0]; + let requestId = args.originalRequestId || args.requestId; + let bid = cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId][0]; if (!bid) { logError(LOG_PRE_FIX + 'Could not find associated bid request for bid response with requestId: ', args.requestId); return; @@ -567,7 +578,9 @@ function bidResponseHandler(args) { if ((bid.bidder && args.bidderCode && bid.bidder !== args.bidderCode) || (bid.bidder === args.bidderCode && bid.status === SUCCESS)) { bid = copyRequiredBidDetails(args); - cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[args.requestId].push(bid); + cache.auctions[args.auctionId].adUnitCodes[args.adUnitCode].bids[requestId].push(bid); + } else if (args.originalRequestId) { + bid.bidId = args.requestId; } if (args.floorData) { @@ -598,7 +611,7 @@ function bidRejectedHandler(args) { function bidderDoneHandler(args) { cache.auctions[args.auctionId].bidderDonePendingCount--; args.bids.forEach(bid => { - let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.requestId]; + let cachedBid = cache.auctions[bid.auctionId].adUnitCodes[bid.adUnitCode].bids[bid.bidId || bid.originalRequestId || bid.requestId]; if (typeof bid.serverResponseTimeMs !== 'undefined') { cachedBid.serverLatencyTimeMs = bid.serverResponseTimeMs; } @@ -613,7 +626,7 @@ function bidderDoneHandler(args) { function bidWonHandler(args) { let auctionCache = cache.auctions[args.auctionId]; - auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.requestId; + auctionCache.adUnitCodes[args.adUnitCode].bidWon = args.originalRequestId || args.requestId; auctionCache.adUnitCodes[args.adUnitCode].bidWonAdId = args.adId; executeBidWonLoggerCall(args.auctionId, args.adUnitCode); } @@ -631,7 +644,7 @@ function bidTimeoutHandler(args) { // db = 0 and t = 1 means bidder did respond with a bid but post timeout args.forEach(badBid => { let auctionCache = cache.auctions[badBid.auctionId]; - let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.requestId ][0]; + let bid = auctionCache.adUnitCodes[badBid.adUnitCode].bids[ badBid.bidId || badBid.originalRequestId || badBid.requestId ][0]; if (bid) { bid.status = ERROR; bid.error = { diff --git a/modules/pubmaticBidAdapter.js b/modules/pubmaticBidAdapter.js index 75f77d16c06..076cd7c3961 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -28,10 +28,11 @@ const AUCTION_TYPE = 1; const UNDEFINED = undefined; const DEFAULT_WIDTH = 0; const DEFAULT_HEIGHT = 0; +const DEFAULT_TTL = 360; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application -const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; +const MSG_VIDEO_PLCMT_MISSING = 'Video.plcmt param missing'; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url @@ -74,6 +75,10 @@ const NATIVE_ASSET_IMAGE_TYPE = { 'IMAGE': 3 } +const BANNER_CUSTOM_PARAMS = { + 'battr': DATA_TYPES.ARRAY +} + const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', @@ -142,6 +147,12 @@ const MEDIATYPE = [ NATIVE ] +const MEDIATYPE_TTL = { + 'banner': 360, + 'video': 1800, + 'native': 1800 +}; + let publisherId = 0; let isInvalidNativeRequest = false; let biddersList = ['pubmatic']; @@ -557,6 +568,14 @@ function _createBannerRequest(bid) { } bannerObj.pos = 0; bannerObj.topframe = inIframe() ? 0 : 1; + + // Adding Banner custom params + const bannerCustomParams = {...deepAccess(bid, 'ortb2Imp.banner')}; + for (let key in BANNER_CUSTOM_PARAMS) { + if (bannerCustomParams.hasOwnProperty(key)) { + bannerObj[key] = _checkParamDataType(key, bannerCustomParams[key], BANNER_CUSTOM_PARAMS[key]); + } + } } else { logWarn(LOG_WARN_PREFIX + 'Error: mediaTypes.banner.size missing for adunit: ' + bid.params.adUnit + '. Ignoring the banner impression in the adunit.'); bannerObj = UNDEFINED; @@ -566,8 +585,8 @@ function _createBannerRequest(bid) { export function checkVideoPlacement(videoData, adUnitCode) { // Check for video.placement property. If property is missing display log message. - if (FEATURES.VIDEO && !deepAccess(videoData, 'placement')) { - logWarn(MSG_VIDEO_PLACEMENT_MISSING + ' for ' + adUnitCode); + if (FEATURES.VIDEO && !deepAccess(videoData, 'plcmt')) { + logWarn(MSG_VIDEO_PLCMT_MISSING + ' for ' + adUnitCode); }; } @@ -669,7 +688,7 @@ function _createImpressionObject(bid, bidderRequest) { var sizes = bid.hasOwnProperty('sizes') ? bid.sizes : []; var mediaTypes = ''; var format = []; - var isFledgeEnabled = bidderRequest?.fledgeEnabled; + var isFledgeEnabled = bidderRequest?.paapi?.enabled; impObj = { id: bid.bidId, @@ -679,7 +698,10 @@ function _createImpressionObject(bid, bidderRequest) { ext: { pmZoneId: _parseSlotParam('pmzoneid', bid.params.pmzoneid) }, - bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY + bidfloorcur: bid.params.currency ? _parseSlotParam('currency', bid.params.currency) : DEFAULT_CURRENCY, + displaymanager: 'Prebid.js', + displaymanagerver: '$prebid.version$', // prebid version + pmp: bid.ortb2Imp?.pmp || undefined }; _addPMPDealsInImpression(impObj, bid); @@ -817,7 +839,7 @@ function _addFloorFromFloorModule(impObj, bid) { sizesArray.forEach(size => { let floorInfo = bid.getFloor({ currency: impObj.bidfloorcur, mediaType: mediaType, size: size }); logInfo(LOG_WARN_PREFIX, 'floor from floor module returned for mediatype:', mediaType, ' and size:', size, ' is: currency', floorInfo.currency, 'floor', floorInfo.floor); - if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === impObj.bidfloorcur && !isNaN(parseInt(floorInfo.floor))) { let mediaTypeFloor = parseFloat(floorInfo.floor); logInfo(LOG_WARN_PREFIX, 'floor from floor module:', mediaTypeFloor, 'previous floor value', bidFloor, 'Min:', Math.min(mediaTypeFloor, bidFloor)); if (bidFloor === -1) { @@ -850,6 +872,23 @@ function _handleEids(payload, validBidRequests) { } } +export function setTTL(bid, newBid) { + let ttl = MEDIATYPE_TTL[newBid?.mediaType] || DEFAULT_TTL; + newBid.ttl = bid.exp || ttl; +} + +// Setting IBV & meta.mediaType field into the bid response +export function setIBVField(bid, newBid) { + if (bid?.ext?.ibv) { + newBid.ext = newBid.ext || {}; + newBid.ext['ibv'] = bid.ext.ibv; + + // Overriding the mediaType field in meta with the `video` value if bid.ext.ibv is present + newBid.meta = newBid.meta || {}; + newBid.meta.mediaType = VIDEO; + } +} + function _checkMediaType(bid, newBid) { // Create a regex here to check the strings if (bid.ext && bid.ext['bidtype'] != undefined) { @@ -999,7 +1038,7 @@ function isNonEmptyArray(test) { * @param {*} bid : bids */ export function prepareMetaObject(br, bid, seat) { - br.meta = {}; + br.meta = br.meta || {}; if (bid.ext && bid.ext.dspid) { br.meta.networkId = bid.ext.dspid; @@ -1038,6 +1077,11 @@ export function prepareMetaObject(br, bid, seat) { if (bid.ext && bid.ext.dsa && Object.keys(bid.ext.dsa).length) { br.meta.dsa = bid.ext.dsa; } + + // Initializing meta.mediaType field to the actual bidType returned by the bidder + if (br.mediaType) { + br.meta.mediaType = br.mediaType; + } } export const spec = { @@ -1098,7 +1142,6 @@ export const spec = { /** * 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) => { @@ -1289,6 +1332,11 @@ export const spec = { } } + // if present, merge device object from ortb2 into `payload.device` + if (bidderRequest?.ortb2?.device) { + mergeDeep(payload.device, bidderRequest.ortb2.device); + } + if (commonFpd.ext?.prebid?.bidderparams?.[bidderRequest.bidderCode]?.acat) { const acatParams = commonFpd.ext.prebid.bidderparams[bidderRequest.bidderCode].acat; _allowedIabCategoriesValidation(payload, acatParams); @@ -1360,7 +1408,7 @@ export const spec = { dealId: bid.dealid, currency: respCur, netRevenue: NET_REVENUE, - ttl: 300, + ttl: DEFAULT_TTL, referrer: parsedReferrer, ad: bid.adm, pm_seat: seatbidder.seat || null, @@ -1371,6 +1419,7 @@ export const spec = { parsedRequest.imp.forEach(req => { if (bid.impid === req.id) { _checkMediaType(bid, newBid); + setTTL(bid, newBid); switch (newBid.mediaType) { case BANNER: break; @@ -1388,12 +1437,12 @@ export const spec = { } }); } + prepareMetaObject(newBid, bid, seatbidder.seat); + setIBVField(bid, newBid); if (bid.ext && bid.ext.deal_channel) { newBid['dealChannel'] = dealChannelValues[bid.ext.deal_channel] || null; } - prepareMetaObject(newBid, bid, seatbidder.seat); - // adserverTargeting if (seatbidder.ext && seatbidder.ext.buyid) { newBid.adserverTargeting = { @@ -1423,7 +1472,7 @@ export const spec = { }); return { bids: bidResponses, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } } catch (error) { diff --git a/modules/pubmaticBidAdapter.md b/modules/pubmaticBidAdapter.md index baf58177505..6fe84d81350 100644 --- a/modules/pubmaticBidAdapter.md +++ b/modules/pubmaticBidAdapter.md @@ -70,6 +70,7 @@ var adVideoAdUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional + plcmt: 1, // optional placement: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional @@ -169,6 +170,7 @@ var adUnits = [ protocols: [ 2, 3 ], // optional battr: [ 13, 14 ], // optional linearity: 1, // optional + plcmt: 1, // optional placement: 2, // optional minbitrate: 10, // optional maxbitrate: 10 // optional @@ -187,7 +189,12 @@ PubMatic recommends the UserSync configuration below. Without it, the PubMatic pbjs.setConfig({ userSync: { iframeEnabled: true, - enabledBidders: ['pubmatic'], + filterSettings: { + iframe: { + bidders: '*', // '*' represents all bidders + filter: 'include' + } + }, syncDelay: 6000 }}); diff --git a/modules/pubriseBidAdapter.js b/modules/pubriseBidAdapter.js new file mode 100644 index 00000000000..646546329db --- /dev/null +++ b/modules/pubriseBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'pubrise'; +const AD_URL = 'https://backend.pubrise.ai/pbjs'; +const SYNC_URL = 'https://sync.pubrise.ai'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/pubriseBidAdapter.md b/modules/pubriseBidAdapter.md new file mode 100755 index 00000000000..4c130954392 --- /dev/null +++ b/modules/pubriseBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: Pubrise Bidder Adapter +Module Type: Pubrise Bidder Adapter +Maintainer: prebid@pubrise.ai +``` + +# Description + +Connects to Pubrise exchange for bids. +Pubrise 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: 'pubrise', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'pubrise', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'pubrise', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` \ No newline at end of file diff --git a/modules/pubwiseBidAdapter.js b/modules/pubwiseBidAdapter.js index eca0c971050..df31dde127b 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.js @@ -1,5 +1,5 @@ -import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError } from '../src/utils.js'; +import { _each, isBoolean, isEmptyStr, isNumber, isStr, deepClone, isArray, deepSetValue, inIframe, mergeDeep, deepAccess, logMessage, logInfo, logWarn, logError, isPlainObject } 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'; @@ -63,6 +63,7 @@ const VIDEO_CUSTOM_PARAMS = { 'battr': DATA_TYPES.ARRAY, 'linearity': DATA_TYPES.NUMBER, 'placement': DATA_TYPES.NUMBER, + 'plcmt': DATA_TYPES.NUMBER, 'minbitrate': DATA_TYPES.NUMBER, 'maxbitrate': DATA_TYPES.NUMBER, 'skip': DATA_TYPES.NUMBER @@ -666,7 +667,7 @@ function _addFloorFromFloorModule(impObj, bid) { [BANNER, VIDEO, NATIVE].forEach(mediaType => { if (impObj.hasOwnProperty(mediaType)) { let floorInfo = bid.getFloor({ currency: impObj.bidFloorCur, mediaType: mediaType, size: '*' }); - if (typeof floorInfo === 'object' && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === impObj.bidFloorCur && !isNaN(parseInt(floorInfo.floor))) { let mediaTypeFloor = parseFloat(floorInfo.floor); bidFloor = (bidFloor == -1 ? mediaTypeFloor : Math.min(mediaTypeFloor, bidFloor)) } diff --git a/modules/pubxaiAnalyticsAdapter.js b/modules/pubxaiAnalyticsAdapter.js index d4a7ec70a70..b2f9af247f6 100644 --- a/modules/pubxaiAnalyticsAdapter.js +++ b/modules/pubxaiAnalyticsAdapter.js @@ -1,208 +1,354 @@ -import { deepAccess, parseSizesInput, getWindowLocation, buildUrl } from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import { + getGptSlotInfoForAdUnitCode, getGptSlotForAdUnitCode +} from '../libraries/gptUtils/gptUtils.js'; +import { getDeviceType, getBrowser, getOS } from '../libraries/userAgentUtils/index.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; +import { sendBeacon } from '../src/ajax.js' import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; -import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { + deepAccess, parseSizesInput, getWindowLocation, buildUrl, cyrb53Hash +} from '../src/utils.js'; + +let initOptions; const emptyUrl = ''; const analyticsType = 'endpoint'; -const pubxaiAnalyticsVersion = 'v1.2.0'; +const adapterCode = 'pubxai'; +const pubxaiAnalyticsVersion = 'v2.1.0'; const defaultHost = 'api.pbxai.com'; const auctionPath = '/analytics/auction'; const winningBidPath = '/analytics/bidwon'; +const storage = getStorageManager({ moduleType: MODULE_TYPE_ANALYTICS, moduleName: adapterCode }) -let initOptions; -let auctionTimestamp; -let auctionCache = []; -let events = { - bids: [], - floorDetail: {}, - pageDetail: {}, - deviceDetail: {} -}; - -function getStorage() { - try { - return window.top['sessionStorage']; - } catch (e) { - return null; +/** + * The sendCache is a global cache object which tracks the pending sends + * back to pubx.ai. The data may be removed from this cache, post send. + */ +export const sendCache = new Proxy( + {}, + { + get: (target, name) => { + if (!target.hasOwnProperty(name)) { + target[name] = []; + } + return target[name]; + }, } -} +); -var pubxaiAnalyticsAdapter = Object.assign(adapter( +/** + * auctionCache is a global cache object which stores all auction histories + * for the session. When getting a key from the auction cache, any + * information already known about the auction or associated data (floor + * data configured by prebid, browser data, user data etc) is added to + * the cache automatically. + */ +export const auctionCache = new Proxy( + {}, { - emptyUrl, - analyticsType - }), { - track({ eventType, args }) { - if (typeof args !== 'undefined') { - if (eventType === EVENTS.BID_TIMEOUT) { - args.forEach(item => { mapBidResponse(item, 'timeout'); }); - } else if (eventType === EVENTS.AUCTION_INIT) { - events.auctionInit = args; - events.floorDetail = {}; - events.bids = []; - const floorData = deepAccess(args, 'bidderRequests.0.bids.0.floorData'); - if (typeof floorData !== 'undefined') { - Object.assign(events.floorDetail, floorData); - } - auctionTimestamp = args.timestamp; - } else if (eventType === EVENTS.BID_RESPONSE) { - mapBidResponse(args, 'response'); - } else if (eventType === EVENTS.BID_WON) { - send({ - winningBid: mapBidResponse(args, 'bidwon') - }, 'bidwon'); + get: (target, name) => { + if (!target.hasOwnProperty(name)) { + target[name] = { + bids: [], + auctionDetail: { + refreshRank: Object.keys(target).length, + auctionId: name, + }, + floorDetail: {}, + pageDetail: { + host: getWindowLocation().host, + path: getWindowLocation().pathname, + search: getWindowLocation().search, + }, + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), + }, + userDetail: { + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), + }, + consentDetail: { + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), + }, + pmacDetail: JSON.parse(storage.getDataFromLocalStorage('pubx:pmac')) || {}, // {auction_1: {floor:0.23,maxBid:0.34,bidCount:3},auction_2:{floor:0.13,maxBid:0.14,bidCount:2} + extraData: JSON.parse(storage.getDataFromLocalStorage('pubx:extraData')) || {}, + initOptions: { + ...initOptions, + auctionId: name, // back-compat + }, + sendAs: [], + }; } - } - if (eventType === EVENTS.AUCTION_END) { - send(events, 'auctionEnd'); - } + return target[name]; + }, } -}); +); -function mapBidResponse(bidResponse, status) { - if (typeof bidResponse !== 'undefined') { - let bid = { - adUnitCode: bidResponse.adUnitCode, - gptSlotCode: getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, - auctionId: bidResponse.auctionId, - bidderCode: bidResponse.bidder, - cpm: bidResponse.cpm, - 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: parseSizesInput(bidResponse.size).toString(), +/** + * Fetch extra ad server data for a specific ad slot (bid) + * @param {object} bid an output from extractBid + * @returns {object} key value pairs from the adserver + */ +const getAdServerDataForBid = (bid) => { + const gptSlot = getGptSlotForAdUnitCode(bid); + if (gptSlot) { + return Object.fromEntries( + gptSlot + .getTargetingKeys() + .filter( + (key) => + key.startsWith('pubx-') || + (key.startsWith('hb_') && (key.match(/_/g) || []).length === 1) + ) + .map((key) => [key, gptSlot.getTargeting(key)]) + ); + } + return {}; // TODO: support more ad servers +}; + +/** + * extracts and derives valuable data from a prebid bidder bidResponse object + * @param {object} bidResponse a prebid bidder bidResponse (see + * https://docs.prebid.org/dev-docs/publisher-api-reference/getBidResponses.html) + * @returns {object} + */ +const extractBid = (bidResponse) => { + return { + adUnitCode: bidResponse.adUnitCode, + gptSlotCode: + getGptSlotInfoForAdUnitCode(bidResponse.adUnitCode).gptSlot || null, + auctionId: bidResponse.auctionId, + bidderCode: bidResponse.bidder, + cpm: bidResponse.cpm, + creativeId: bidResponse.creativeId, + dealId: bidResponse.dealId, + currency: bidResponse.currency, + floorData: bidResponse.floorData, + mediaType: bidResponse.mediaType, + netRevenue: bidResponse.netRevenue, + requestTimestamp: bidResponse.requestTimestamp, + responseTimestamp: bidResponse.responseTimestamp, + status: bidResponse.status, + sizes: parseSizesInput(bidResponse.size).toString(), + statusMessage: bidResponse.statusMessage, + timeToRespond: bidResponse.timeToRespond, + transactionId: bidResponse.transactionId, + bidId: bidResponse.bidId || bidResponse.requestId, + placementId: bidResponse.params + ? deepAccess(bidResponse, 'params.0.placementId') + : null, + source: bidResponse.source || 'null', + }; +}; + +/** + * Track the events emitted by prebid and handle each case. See https://docs.prebid.org/dev-docs/publisher-api-reference/getEvents.html for more info + * @param {object} event the prebid event emmitted + * @param {string} event.eventType the type of the event + * @param {object} event.args the arguments of the emitted event + */ +const track = ({ eventType, args }) => { + switch (eventType) { + // handle invalid bids, and remove them from the adUnit cache + case EVENTS.BID_TIMEOUT: + args.map(extractBid).forEach((bid) => { + bid.bidType = 3; + auctionCache[bid.auctionId].bids.push(bid); + }); + break; + // handle valid bid responses and record them as part of an auction + case EVENTS.BID_RESPONSE: + const bid = Object.assign(extractBid(args), { bidType: 2 }); + auctionCache[bid.auctionId].bids.push(bid); + break; + case EVENTS.BID_REJECTED: + const rejectedBid = Object.assign(extractBid(args), { bidType: 1 }); + auctionCache[rejectedBid.auctionId].bids.push(rejectedBid); + break; + // capture extra information from the auction, and if there were no bids + // (and so no chance of a win) send the auction + case EVENTS.AUCTION_END: + Object.assign( + auctionCache[args.auctionId].floorDetail, + args.adUnits + .map((i) => i?.bids.length && i.bids[0]?.floorData) + .find((i) => i) || {} + ); + auctionCache[args.auctionId].deviceDetail.cdep = args.bidderRequests + .map((bidRequest) => bidRequest.ortb2?.device?.ext?.cdep) + .find((i) => i); + Object.assign(auctionCache[args.auctionId].auctionDetail, { + adUnitCodes: args.adUnits.map((i) => i.code), + timestamp: args.timestamp, }); - events.bids.push(bid); - } else { - Object.assign(bid, { - bidId: bidResponse.requestId, - floorProvider: events.floorDetail?.floorProvider || null, - floorFetchStatus: events.floorDetail?.fetchStatus || null, - floorLocation: events.floorDetail?.location || null, - floorModelVersion: events.floorDetail?.modelVersion || null, - floorSkipRate: events.floorDetail?.skipRate || 0, - isFloorSkipped: events.floorDetail?.skipped || false, + if ( + auctionCache[args.auctionId].bids.every((bid) => [1, 3].includes(bid.bidType)) + ) { + prepareSend(args.auctionId); + } + break; + // send the prebid winning bid back to pubx + case EVENTS.BID_WON: + const winningBid = extractBid(args); + const floorDetail = auctionCache[winningBid.auctionId].floorDetail; + Object.assign(winningBid, { + floorProvider: floorDetail?.floorProvider || null, + floorFetchStatus: floorDetail?.fetchStatus || null, + floorLocation: floorDetail?.location || null, + floorModelVersion: floorDetail?.modelVersion || null, + floorSkipRate: floorDetail?.skipRate || 0, + isFloorSkipped: floorDetail?.skipped || false, isWinningBid: true, - placementId: bidResponse.params ? deepAccess(bidResponse, 'params.0.placementId') : null, - renderedSize: bidResponse.size, - renderStatus: 4 + renderedSize: args.size, + bidType: 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'; + winningBid.adServerData = getAdServerDataForBid(winningBid); + auctionCache[winningBid.auctionId].winningBid = winningBid; + prepareSend(winningBid.auctionId); + break; + // do nothing + default: + break; } - 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)); +/** + * If true, send data back to pubxai + * @param {string} auctionId + * @param {number} samplingRate + * @returns {boolean} + */ +const shouldFireEventRequest = (auctionId, samplingRate = 1) => { + return parseInt(cyrb53Hash(auctionId)) % samplingRate === 0; }; -function send(data, status) { - if (pubxaiAnalyticsAdapter.shouldFireEventRequest(initOptions.samplingRate)) { - let location = getWindowLocation(); - const storage = getStorage(); - data.initOptions = initOptions; - data.pageDetail = {}; - Object.assign(data.pageDetail, { - host: location.host, - path: location.pathname, - search: location.search - }); - if (typeof data !== 'undefined' && typeof data.auctionInit !== 'undefined') { - data.pageDetail.adUnits = data.auctionInit.adUnitCodes; - data.initOptions.auctionId = data.auctionInit.auctionId; - delete data.auctionInit; - - data.pmcDetail = {}; - Object.assign(data.pmcDetail, { - bidDensity: storage ? storage.getItem('pbx:dpbid') : null, - maxBid: storage ? storage.getItem('pbx:mxbid') : null, - auctionId: storage ? storage.getItem('pbx:aucid') : null, - }); +/** + * prepare the payload for sending auction data back to pubx.ai + * @param {string} auctionId the auction to send + */ +const prepareSend = (auctionId) => { + const auctionData = Object.assign({}, auctionCache[auctionId]); + if (!shouldFireEventRequest(auctionId, initOptions.samplingRate)) { + return; + } + [ + { + path: winningBidPath, + requiredKeys: [ + 'winningBid', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'extraData', + 'initOptions', + ], + eventType: 'win', + }, + { + path: auctionPath, + requiredKeys: [ + 'bids', + 'pageDetail', + 'deviceDetail', + 'floorDetail', + 'auctionDetail', + 'userDetail', + 'consentDetail', + 'pmacDetail', + 'extraData', + 'initOptions', + ], + eventType: 'auction', + }, + ].forEach(({ path, requiredKeys, eventType }) => { + const data = Object.fromEntries( + requiredKeys.map((key) => [key, auctionData[key]]) + ); + if ( + auctionCache[auctionId].sendAs.includes(eventType) || + !requiredKeys.every((key) => !!auctionData[key]) + ) { + return; } - data.deviceDetail = {}; - Object.assign(data.deviceDetail, { - platform: navigator.platform, - deviceType: getDeviceType(), - deviceOS: getOS(), - browser: getBrowser() - }); - - let pubxaiAnalyticsRequestUrl = buildUrl({ + const pubxaiAnalyticsRequestUrl = buildUrl({ protocol: 'https', - hostname: (initOptions && initOptions.hostName) || defaultHost, - pathname: status == 'bidwon' ? winningBidPath : auctionPath, + hostname: + (auctionData.initOptions && auctionData.initOptions.hostName) || + defaultHost, + pathname: path, search: { - auctionTimestamp: auctionTimestamp, + auctionTimestamp: auctionData.auctionDetail.timestamp, pubxaiAnalyticsVersion: pubxaiAnalyticsVersion, - prebidVersion: getGlobal().version + prebidVersion: '$prebid.version$', + pubxId: initOptions.pubxId, + }, + }); + sendCache[pubxaiAnalyticsRequestUrl].push(data); + auctionCache[auctionId].sendAs.push(eventType); + }); +}; + +const send = () => { + const toBlob = (d) => new Blob([JSON.stringify(d)], { type: 'text/json' }); + + Object.entries(sendCache).forEach(([requestUrl, events]) => { + let payloadStart = 0; + + events.forEach((event, index, arr) => { + const payload = arr.slice(payloadStart, index + 2); + const payloadTooLarge = toBlob(payload).size > 65536; + + if (payloadTooLarge || index + 1 === arr.length) { + sendBeacon( + requestUrl, + toBlob(payloadTooLarge ? payload.slice(0, -1) : payload) + ); + payloadStart = index; } }); - 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); + + events.splice(0); + }); +}; + +// register event listener to send logs when user leaves page +if (document.visibilityState) { + document.addEventListener('visibilitychange', () => { + if (document.visibilityState === 'hidden') { + send(); } - } + }); } -pubxaiAnalyticsAdapter.originEnableAnalytics = pubxaiAnalyticsAdapter.enableAnalytics; -pubxaiAnalyticsAdapter.enableAnalytics = function (config) { +// declare the analytics adapter +var pubxaiAnalyticsAdapter = Object.assign( + adapter({ + emptyUrl, + analyticsType, + }), + { track } +); + +pubxaiAnalyticsAdapter.originEnableAnalytics = + pubxaiAnalyticsAdapter.enableAnalytics; +pubxaiAnalyticsAdapter.enableAnalytics = (config) => { initOptions = config.options; pubxaiAnalyticsAdapter.originEnableAnalytics(config); }; adapterManager.registerAnalyticsAdapter({ adapter: pubxaiAnalyticsAdapter, - code: 'pubxai' + code: adapterCode, }); export default pubxaiAnalyticsAdapter; diff --git a/modules/pubxaiRtdProvider.js b/modules/pubxaiRtdProvider.js index b958856df00..4528b29cf11 100644 --- a/modules/pubxaiRtdProvider.js +++ b/modules/pubxaiRtdProvider.js @@ -2,6 +2,8 @@ import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { submodule } from '../src/hook.js'; import { deepAccess } from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { getStorageManager } from '../src/storageManager.js'; /** * This RTD module has a dependency on the priceFloors module. * We utilize the createFloorsDataForAuction function from the priceFloors module to incorporate price floors data into the current auction. @@ -16,6 +18,7 @@ export const FloorsApiStatus = Object.freeze({ SUCCESS: 'SUCCESS', ERROR: 'ERROR', }); +export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); export const FLOORS_EVENT_HANDLE = 'floorsApi'; export const FLOORS_END_POINT = 'https://floor.pbxai.com/'; export const FLOOR_PROVIDER = 'PubxFloorProvider'; @@ -80,31 +83,48 @@ export const setFloorsApiStatus = (status) => { export const getUrl = (provider) => { const { pubxId, endpoint } = deepAccess(provider, 'params'); - return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${ - window.location.href - }`; + if (!endpoint) { + return null; // Indicate that no endpoint is provided + } + return `${endpoint || FLOORS_END_POINT}?pubxId=${pubxId}&page=${window.location.href}`; }; export const fetchFloorRules = async (provider) => { return new Promise((resolve, reject) => { setFloorsApiStatus(FloorsApiStatus.IN_PROGRESS); - ajax(getUrl(provider), { - success: (responseText, response) => { - try { - if (response && response.response) { - const floorsResponse = JSON.parse(response.response); - resolve(floorsResponse); - } else { - resolve(null); + const url = getUrl(provider); + if (url) { + // Fetch from remote endpoint + ajax(url, { + success: (responseText, response) => { + try { + if (response && response.response) { + const floorsResponse = JSON.parse(response.response); + resolve(floorsResponse); + } else { + resolve(null); + } + } catch (error) { + reject(error); } - } catch (error) { - reject(error); + }, + error: (responseText, response) => { + reject(response); + }, + }); + } else { + // Fetch from local storage + try { + const localData = storage.getDataFromSessionStorage('pubx:dynamicFloors') || window.__pubxDynamicFloors__; + if (localData) { + resolve(JSON.parse(localData)); + } else { + resolve(null); } - }, - error: (responseText, response) => { - reject(response); - }, - }); + } catch (error) { + reject(error); + } + } }); }; diff --git a/modules/pubxaiRtdProvider.md b/modules/pubxaiRtdProvider.md index d7d89857c62..2b89d3baa04 100644 --- a/modules/pubxaiRtdProvider.md +++ b/modules/pubxaiRtdProvider.md @@ -32,7 +32,7 @@ pbjs.setConfig({ ..., realTimeData: { auctionDelay: AUCTION_DELAY, - dataProviders: { + dataProviders: [{ name: "pubxai", waitForIt: true, params: { @@ -42,7 +42,7 @@ pbjs.setConfig({ enforcement: ``, // (optional) data: `` // (optional) } - } + }] } // rest of the config ..., diff --git a/modules/pulsepointBidAdapter.js b/modules/pulsepointBidAdapter.js index 516254b358b..50747616872 100644 --- a/modules/pulsepointBidAdapter.js +++ b/modules/pulsepointBidAdapter.js @@ -1,7 +1,6 @@ import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import {isArray} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const DEFAULT_CURRENCY = 'USD'; const KNOWN_PARAMS = ['cp', 'ct', 'cf', 'battr', 'deals']; @@ -58,13 +57,6 @@ export const spec = { url: 'https://bh.contextweb.com/visitormatch/prebid' }]; } - }, - transformBidParams: function(params) { - return convertTypes({ - 'cf': 'string', - 'cp': 'number', - 'ct': 'number' - }, params); } }; diff --git a/modules/qortexRtdProvider.js b/modules/qortexRtdProvider.js index 88b4339b38e..1ee8f8cf5d0 100644 --- a/modules/qortexRtdProvider.js +++ b/modules/qortexRtdProvider.js @@ -1,14 +1,21 @@ import { submodule } from '../src/hook.js'; -import { ajax } from '../src/ajax.js'; import { logWarn, mergeDeep, logMessage, generateUUID } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; import * as events from '../src/events.js'; import { EVENTS } from '../src/constants.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; -let requestUrl; -let bidderArray; -let impressionIds; -let currentSiteContext; +const qortexSessionInfo = {}; +const QX_IN_MESSAGE = { + BID_ENRICH_INITIALIZED: 'CX-BID-ENRICH-INITIALIZED', + DISPATCH_CONTEXT: 'DISPATCH-CONTEXT' +} +const QX_OUT_MESSAGE = { + AUCTION_END: 'AUCTION-END', + NO_CONTEXT: 'NO-CONTEXT', + RTD_INITIALIZED: 'RTD-INITIALIZED', + REQUEST_CONTEXT: 'REQUEST-CONTEXT' +} /** * Init if module configuration is valid @@ -21,11 +28,16 @@ function init (config) { return false; } else { initializeModuleData(config); + if (config?.params?.enableBidEnrichment) { + initializeBidEnrichment(); + } else { + logWarn('Bid Enrichment Function has been disabled in module configuration') + } + if (config?.params?.tagConfig) { + loadScriptTag(config) + } + return true; } - if (config?.params?.tagConfig) { - loadScriptTag(config) - } - return true; } /** @@ -34,64 +46,47 @@ function init (config) { * @param {Function} callback Called on completion */ function getBidRequestData (reqBidsConfig, callback) { - if (reqBidsConfig?.adUnits?.length > 0) { - getContext() - .then(contextData => { - setContextData(contextData) - addContextToRequests(reqBidsConfig) - callback(); - }) - .catch((e) => { - logWarn(e?.message); - callback(); - }); + if (reqBidsConfig?.adUnits?.length > 0 && shouldAllowBidEnrichment()) { + addContextToRequests(reqBidsConfig); } else { - logWarn('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfig)) - callback(); + logWarn('Module function is paused due to configuration \n Module Config: ' + JSON.stringify(reqBidsConfig)); } + callback(); } /** - * determines whether to send a request to context api and does so if necessary - * @returns {Promise} ortb Content object + * Processess auction end events for Qortex reporting + * @param {Object} data Auction end object */ -export function getContext () { - if (!currentSiteContext) { - logMessage('Requesting new context data'); - return new Promise((resolve, reject) => { - const callbacks = { - success(text, data) { - const result = data.status === 200 ? JSON.parse(data.response)?.content : null; - resolve(result); - }, - error(error) { - reject(new Error(error)); - } - } - ajax(requestUrl, callbacks) - }) - } else { - logMessage('Adding Content object from existing context data'); - return new Promise(resolve => resolve(currentSiteContext)); +function onAuctionEndEvent (data, config, t) { + logMessage('Auction ended: ', JSON.stringify(data)); + if (shouldAllowBidEnrichment()) { + if (!qortexSessionInfo.auctionsEnded) { + qortexSessionInfo.auctionsEnded = []; + } + qortexSessionInfo.auctionsEnded.push(JSON.stringify(data)); + postBidEnrichmentMessage(QX_OUT_MESSAGE.AUCTION_END, JSON.stringify(data)); } } /** * Updates bidder configs with the response from Qortex context services * @param {Object} reqBidsConfig Bid request configuration object - * @param {string[]} bidders Bidders specified in module's configuration */ export function addContextToRequests (reqBidsConfig) { - if (currentSiteContext === null) { + if (qortexSessionInfo.currentSiteContext === null) { logWarn('No context data received at this time'); + requestContextData(); } else { - const fragment = { site: {content: currentSiteContext} } - if (bidderArray?.length > 0) { - bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})) - } else if (!bidderArray) { - mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment); - } else { - logWarn('Config contains an empty bidders array, unable to determine which bids to enrich'); + if (checkPercentageOutcome(qortexSessionInfo.groupConfig?.prebidBidEnrichmentPercentage)) { + const fragment = { site: {content: qortexSessionInfo.currentSiteContext} } + if (qortexSessionInfo.bidderArray?.length > 0) { + qortexSessionInfo.bidderArray.forEach(bidder => mergeDeep(reqBidsConfig.ortb2Fragments.bidder, {[bidder]: fragment})); + } else if (!qortexSessionInfo.bidderArray) { + mergeDeep(reqBidsConfig.ortb2Fragments.global, fragment); + } else { + logWarn('Config contains an empty bidders array, unable to determine which bids to enrich'); + } } } } @@ -121,22 +116,43 @@ export function loadScriptTag(config) { switch (e?.detail?.type) { case 'qx-impression': const {uid} = e.detail; - if (!uid || impressionIds.has(uid)) { - logWarn(`received invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`) + if (!uid || qortexSessionInfo.impressionIds.has(uid)) { + logWarn(`Received invalid billable event due to ${!uid ? 'missing' : 'duplicate'} uid: qx-impression`) return; } else { - logMessage('received billable event: qx-impression') - impressionIds.add(uid) + logMessage('Received billable event: qx-impression') + qortexSessionInfo.impressionIds.add(uid) billableEvent.transactionId = e.detail.uid; events.emit(EVENTS.BILLABLE_EVENT, billableEvent); break; } default: - logWarn(`received invalid billable event: ${e.detail?.type}`) + logWarn(`Received invalid billable event: ${e.detail?.type}`) } }) - loadExternalScript(src, code, undefined, undefined, attr); + loadExternalScript(src, MODULE_TYPE_RTD, code, undefined, undefined, attr); +} + +/** + * Request contextual data about page (after checking for allow) and begin listening for postMessages from publisher + */ +export function initializeBidEnrichment() { + if (shouldAllowBidEnrichment()) { + requestContextData() + } + addEventListener('message', windowPostMessageReceived); +} + +/** + * Call Qortex code on page for available contextual information about current environment + */ +export function requestContextData() { + if (qortexSessionInfo.currentSiteContext) { + logMessage('Context data already retrieved.'); + } else { + postBidEnrichmentMessage(QX_OUT_MESSAGE.REQUEST_CONTEXT); + } } /** @@ -144,22 +160,106 @@ export function loadScriptTag(config) { * @param {Object} config module config obtained during init */ export function initializeModuleData(config) { - const DEFAULT_API_URL = 'https://demand.qortex.ai'; - const {apiUrl, groupId, bidders} = config.params; - requestUrl = `${apiUrl || DEFAULT_API_URL}/api/v1/analyze/${groupId}/prebid`; - bidderArray = bidders; - impressionIds = new Set(); - currentSiteContext = null; + const {groupId, bidders, enableBidEnrichment} = config.params; + qortexSessionInfo.bidEnrichmentDisabled = enableBidEnrichment !== null ? !enableBidEnrichment : true; + qortexSessionInfo.bidderArray = bidders; + qortexSessionInfo.impressionIds = new Set(); + qortexSessionInfo.currentSiteContext = null; + qortexSessionInfo.sessionId = generateSessionId(); + qortexSessionInfo.groupId = groupId; + return qortexSessionInfo; } +/** + * Allows setting of contextual data + */ export function setContextData(value) { - currentSiteContext = value + qortexSessionInfo.currentSiteContext = value +} + +/** + * Allows setting of group configuration data + */ +export function setGroupConfigData(value) { + qortexSessionInfo.groupConfig = value +} + +/** + * Unique id generator creating an identifier through datetime and random number + * @returns {string} + */ +function generateSessionId() { + const randomInt = window.crypto.getRandomValues(new Uint32Array(1)); + const currentDateTime = Math.floor(Date.now() / 1000); + return 'QX' + randomInt.toString() + 'X' + currentDateTime.toString() +} + +/** + * Check for a random value to be above given percentage threshold + * @param {number} percentageValue 0-100 number for percentage check. + * @returns {Boolean} + */ +function checkPercentageOutcome(percentageValue) { + return (percentageValue ?? 0) > (Math.random() * 100); +} + +/** + * Check for allowing functionality of bid enrichment capabilities. + * @returns {Boolean} + */ +function shouldAllowBidEnrichment() { + if (qortexSessionInfo.bidEnrichmentDisabled) { + logWarn('Bid enrichment disabled at prebid config') + return false; + } + return true +} + +/** + * Passes message out to external page through postMessage method + * @param {string} msg message string to be passed to CX-BID-ENRICH target on current page + * @param {Object} data optional parameter object with additional data to send with post + */ +function postBidEnrichmentMessage(msg, data = null) { + window.postMessage({ + target: 'CX-BID-ENRICH', + message: msg, + params: data + }, window.location.protocol + '//' + window.location.host); + logMessage('Dispatching window postMessage: ' + msg); +} + +/** + * Receives messages passed through postMessage method to QORTEX-PREBIDJS-RTD-MODULE on current page + * @param {Object} evt data object holding Event information + */ +export function windowPostMessageReceived(evt) { + const data = evt.data; + if (typeof data.target !== 'undefined' && data.target === 'QORTEX-PREBIDJS-RTD-MODULE') { + if (shouldAllowBidEnrichment()) { + if (data.message === QX_IN_MESSAGE.BID_ENRICH_INITIALIZED) { + if (Boolean(data.params) && Boolean(data.params?.groupConfig)) { + setGroupConfigData(data.params.groupConfig); + } + postBidEnrichmentMessage(QX_OUT_MESSAGE.RTD_INITIALIZED); + if (qortexSessionInfo?.auctionsEnded?.length > 0) { + qortexSessionInfo.auctionsEnded.forEach(data => postBidEnrichmentMessage(QX_OUT_MESSAGE.AUCTION_END, data)); + } + requestContextData(); + } else if (data.message === QX_IN_MESSAGE.DISPATCH_CONTEXT) { + if (data.params?.context) { + setContextData(data.params.context); + } + } + } + } } export const qortexSubmodule = { name: 'qortex', init, - getBidRequestData + getBidRequestData, + onAuctionEndEvent } submodule('realTimeData', qortexSubmodule); diff --git a/modules/qortexRtdProvider.md b/modules/qortexRtdProvider.md index 312696068cd..b9a08eb817a 100644 --- a/modules/qortexRtdProvider.md +++ b/modules/qortexRtdProvider.md @@ -12,7 +12,7 @@ Maintainer: mannese@qortex.ai The Qortex RTD module appends contextual segments to the bidding object based on the content of a page using the Qortex API. -Upon load, the Qortex context API will analyze the bidder page (video, text, image, etc.) and will return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26). The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data. +If the `Qortex Group Id` and module parameters provided during configuration is active, the Qortex context API will attempt to generate and return a [Content object](https://www.iab.com/wp-content/uploads/2016/03/OpenRTB-API-Specification-Version-2-5-FINAL.pdf#page=26) using indexed data from provided page content. The module will then merge that object into the appropriate bidders' `ortb2.site.content`, which can be used by prebid adapters that use `site.content` data. ## Build @@ -40,13 +40,8 @@ pbjs.setConfig({ params: { groupId: 'ABC123', //required bidders: ['qortex', 'adapter2'], //optional (see below) - tagConfig: { // optional, please reach out to your account manager for configuration reccommendation - videoContainer: 'string', - htmlContainer: 'string', - attachToTop: 'string', - esm6Mod: 'string', - continuousLoad: 'string' - } + enableBidEnrichment: true, //optional (see below) + tagConfig: { } // optional, please reach out to your account manager for configuration reccommendation } }] } @@ -63,7 +58,11 @@ pbjs.setConfig({ - If this parameter is omitted, the RTD module will default to updating `ortb2.site.content` on *all* bid adapters being used on the page +#### `enableBidEnrichment` - optional +- This optional parameter allows a publisher to opt-in to the features of the RTD module that use our API to enrich bids with first party data for contextuality. Enabling this feature will allow this module to interact with the Qortex AI contextuality server for indexing and analysis. Please use caution when adding this module to pages that may contain personal user data or proprietary information. + #### `tagConfig` - optional - This optional parameter is an object containing the config settings that could be usedto initialize the Qortex integration on your page. A preconfigured object for this step will be provided to you by the Qortex team. -- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal. \ No newline at end of file +- If this parameter is not present, the Qortex integration can still be configured and loaded manually on your page outside of prebid. The RTD module will continue to initialize and operate as normal. + \ No newline at end of file diff --git a/modules/qtBidAdapter.js b/modules/qtBidAdapter.js new file mode 100644 index 00000000000..cbfd3586fe5 --- /dev/null +++ b/modules/qtBidAdapter.js @@ -0,0 +1,21 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'qt'; +const GVLID = 1331; +const AD_URL = 'https://endpoint1.qt.io/pbjs'; +const SYNC_URL = 'https://cs.qt.io'; + +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/qtBidAdapter.md b/modules/qtBidAdapter.md new file mode 100644 index 00000000000..6d505b38379 --- /dev/null +++ b/modules/qtBidAdapter.md @@ -0,0 +1,79 @@ +# Overview + +``` +Module Name: QT Bidder Adapter +Module Type: QT Bidder Adapter +Maintainer: octavian.cimpu@qt.io +``` + +# Description + +Connects to QT exchange for bids. +QT 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: 'qt', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'qt', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'qt', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/quantcastBidAdapter.js b/modules/quantcastBidAdapter.js index 1ba23302367..ea907f0429c 100644 --- a/modules/quantcastBidAdapter.js +++ b/modules/quantcastBidAdapter.js @@ -49,7 +49,6 @@ function makeVideoImp(bid) { maxbitrate: video.maxbitrate, playbackmethod: video.playbackmethod, delivery: video.delivery, - placement: video.placement, api: video.api, w: video.w, h: video.h @@ -58,7 +57,7 @@ function makeVideoImp(bid) { return { video: videoCopy, placementCode: bid.placementCode, - bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR + bidFloor: DEFAULT_BID_FLOOR }; } @@ -76,7 +75,7 @@ function makeBannerImp(bid) { }) }, placementCode: bid.placementCode, - bidFloor: bid.params.bidFloor || DEFAULT_BID_FLOOR + bidFloor: DEFAULT_BID_FLOOR }; } diff --git a/modules/raynRtdProvider.js b/modules/raynRtdProvider.js index d558c360c4a..ee3d18be381 100644 --- a/modules/raynRtdProvider.js +++ b/modules/raynRtdProvider.js @@ -14,6 +14,7 @@ import { deepAccess, deepSetValue, logError, logMessage, mergeDeep } from '../sr const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'rayn'; const RAYN_TCF_ID = 1220; +const RAYN_PERSONA_TAXONOMY_ID = 103015; const LOG_PREFIX = 'RaynJS: '; export const SEGMENTS_RESOLVER = 'rayn.io'; export const RAYN_LOCAL_STORAGE_KEY = 'rayn-segtax'; @@ -77,6 +78,32 @@ export function generateOrtbDataObject(segtax, segment, maxTier) { }; } +/** + * Create and return ORTB2 object with segtax and personaIds + * @param {number} segtax + * @param {Array} personaIds + * @return {Array} + */ +export function generatePersonaOrtbDataObject(segtax, personaIds) { + const segmentIds = []; + + try { + segmentIds.push(...personaIds.map((id) => { + return { id }; + })) + } catch (error) { + logError(LOG_PREFIX, error); + } + + return { + name: SEGMENTS_RESOLVER, + ext: { + segtax, + }, + segment: segmentIds, + }; +} + /** * Generates checksum * @param {string} url @@ -127,8 +154,14 @@ export function setSegmentsAsBidderOrtb2(bidConfig, bidders, integrationConfig, deepSetValue(raynOrtb2, 'site.content.data', raynContentData); } + const raynUserData = []; if (integrationConfig.iabAudienceCategories.v1_1.enabled && segments[4]) { - const raynUserData = [generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)]; + raynUserData.push(generateOrtbDataObject(4, segments[4], integrationConfig.iabAudienceCategories.v1_1.tier)); + } + if (segments[RAYN_PERSONA_TAXONOMY_ID]) { + raynUserData.push(generatePersonaOrtbDataObject(RAYN_PERSONA_TAXONOMY_ID, segments[RAYN_PERSONA_TAXONOMY_ID])); + } + if (raynUserData.length > 0) { deepSetValue(raynOrtb2, 'user.data', raynUserData); } @@ -163,8 +196,8 @@ function alterBidRequests(reqBidsConfigObj, callback, config, userConsent) { segments[checksum] || (segments[4] && integrationConfig.iabAudienceCategories.v1_1.enabled && !integrationConfig.iabContentCategories.v2_2.enabled && - !integrationConfig.iabContentCategories.v3_0.enabled - ) + !integrationConfig.iabContentCategories.v3_0.enabled) || + segments[RAYN_PERSONA_TAXONOMY_ID] )) { logMessage(LOG_PREFIX, `Segtax data from localStorage: ${JSON.stringify(segments)}`); setSegmentsAsBidderOrtb2(reqBidsConfigObj, bidders, integrationConfig, segments, checksum); diff --git a/modules/readpeakBidAdapter.js b/modules/readpeakBidAdapter.js index 4ff51aeb43e..da3153c0b68 100644 --- a/modules/readpeakBidAdapter.js +++ b/modules/readpeakBidAdapter.js @@ -136,7 +136,7 @@ function impression(slot) { mediaType: 'native', size: '\*' }); - bidFloorFromModule = floorInfo.currency === 'USD' ? floorInfo.floor : undefined; + bidFloorFromModule = floorInfo?.currency === 'USD' ? floorInfo?.floor : undefined; } const imp = { id: slot.bidId, diff --git a/modules/redtramBidAdapter.js b/modules/redtramBidAdapter.js index e1dc0e2a148..bacc209f991 100644 --- a/modules/redtramBidAdapter.js +++ b/modules/redtramBidAdapter.js @@ -1,155 +1,21 @@ -import { - isFn, - isStr, - deepAccess, - getWindowTop, - triggerPixel -} from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { bidWinReport, buildBidRequests, buildUserSyncs, interpretResponse, isBidRequestValid } from '../libraries/precisoUtils/bidUtilsCommon.js'; const BIDDER_CODE = 'redtram'; const AD_URL = 'https://prebid.redtram.com/pbjs'; const SYNC_URL = 'https://prebid.redtram.com/sync'; -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); - default: - return false; - } -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER], - - isBidRequestValid: (bid) => { - return Boolean(bid.bidId && bid.params && bid.params.placementId); - }, - - buildRequests: (validBidRequests = [], bidderRequest) => { - const winTop = 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] : '', - 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) - }; - - if (typeof bid.userId !== 'undefined') { - placement.userId = bid.userId; - } - - const mediaType = bid.mediaTypes; - - if (mediaType && mediaType[BANNER] && mediaType[BANNER].sizes) { - placement.sizes = mediaType[BANNER].sizes; - placement.adFormat = BANNER; - } - - 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; - }, - + isBidRequestValid: isBidRequestValid, + buildRequests: buildBidRequests(AD_URL), + interpretResponse: interpretResponse, 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 - }]; + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, SYNC_URL); }, - - onBidWon: (bid) => { - const cpm = deepAccess(bid, 'adserverTargeting.hb_pb') || ''; - if (isStr(bid.nurl) && bid.nurl !== '') { - bid.nurl = bid.nurl.replace(/\${AUCTION_PRICE}/, cpm); - triggerPixel(bid.nurl); - } - } + onBidWon: bidWinReport }; registerBidder(spec); diff --git a/modules/relaidoBidAdapter.js b/modules/relaidoBidAdapter.js index a180d04cc71..69f9be8d107 100644 --- a/modules/relaidoBidAdapter.js +++ b/modules/relaidoBidAdapter.js @@ -19,7 +19,7 @@ import { isSlotMatchingAdUnitCode } from '../libraries/gptUtils/gptUtils.js'; const BIDDER_CODE = 'relaido'; const BIDDER_DOMAIN = 'api.relaido.jp'; -const ADAPTER_VERSION = '1.2.0'; +const ADAPTER_VERSION = '1.2.1'; const DEFAULT_TTL = 300; const UUID_KEY = 'relaido_uuid'; @@ -270,6 +270,7 @@ function outstreamRender(bid) { height: bid.height, vastXml: bid.vastXml, mediaType: bid.mediaType, + placementId: bid.placementId, }); }); } diff --git a/modules/relevatehealthBidAdapter.js b/modules/relevatehealthBidAdapter.js new file mode 100644 index 00000000000..560dbdeac3e --- /dev/null +++ b/modules/relevatehealthBidAdapter.js @@ -0,0 +1,144 @@ +import { formatResponse } from '../libraries/deepintentUtils/index.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + BANNER +} from '../src/mediaTypes.js'; +import { + deepAccess, + generateUUID, + isArray, + logError +} from '../src/utils.js'; +const BIDDER_CODE = 'relevatehealth'; +const ENDPOINT_URL = 'https://rtb.relevate.health/prebid/relevate'; + +function buildRequests(bidRequests, bidderRequest) { + const requests = []; + // Loop through each bid request + bidRequests.forEach(bid => { + // Construct the bid request object + const request = { + id: generateUUID(), + placementId: bid.params.placement_id, + imp: [{ + id: bid.bidId, + banner: getBanner(bid), + bidfloor: getFloor(bid) + }], + site: getSite(bidderRequest), + user: buildUser(bid) + }; + // Get uspConsent from bidderRequest + if (bidderRequest && bidderRequest.uspConsent) { + request.us_privacy = bidderRequest.uspConsent; + } + // Get GPP Consent from bidderRequest + if (bidderRequest?.gppConsent?.gppString) { + request.gpp = bidderRequest.gppConsent.gppString; + request.gpp_sid = bidderRequest.gppConsent.applicableSections; + } else if (bidderRequest?.ortb2?.regs?.gpp) { + request.gpp = bidderRequest.ortb2.regs.gpp; + request.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + } + // Get coppa compliance from bidderRequest + if (bidderRequest?.ortb2?.regs?.coppa) { + request.coppa = 1; + } + // Push the constructed bid request to the requests array + requests.push(request); + }); + // Return the array of bid requests + return { + method: 'POST', + url: ENDPOINT_URL, + data: JSON.stringify(requests), + options: { + contentType: 'application/json', + } + }; +} +// Format the response as per the standards +function interpretResponse(bidResponse, bidRequest) { + let resp = []; + if (bidResponse && bidResponse.body) { + try { + let bids = bidResponse.body.seatbid && bidResponse.body.seatbid[0] ? bidResponse.body.seatbid[0].bid : []; + if (bids) { + bids.forEach(bidObj => { + let newBid = formatResponse(bidObj); + newBid.mediaType = BANNER; + resp.push(newBid); + }); + } + } catch (err) { + logError(err); + } + } + return resp; +} +// Function to check if Bid is valid +function isBidRequestValid(bid) { + return !!(bid.params.placement_id && bid.params.user_id); +} +// Function to get banner details +function getBanner(bid) { + if (deepAccess(bid, 'mediaTypes.banner')) { + // Fetch width and height from MediaTypes object, if not provided in bid params + if (deepAccess(bid, 'mediaTypes.banner.sizes') && !bid.params.height && !bid.params.width) { + let sizes = deepAccess(bid, 'mediaTypes.banner.sizes'); + if (isArray(sizes) && sizes.length > 0) { + return { + h: sizes[0][1], + w: sizes[0][0] + }; + } + } else { + return { + h: bid.params.height, + w: bid.params.width + }; + } + } +} +// Function to get bid_floor +function getFloor(bid) { + if (bid.params && bid.params.bid_floor) { + return bid.params.bid_floor; + } else { + return 0; + } +} +// Function to get site details +function getSite(bidderRequest) { + let site = {}; + if (bidderRequest && bidderRequest.refererInfo && bidderRequest.refererInfo.page) { + site.name = bidderRequest.refererInfo.domain; + } else { + site.name = ''; + } + return site; +} +// Function to build the user object +function buildUser(bid) { + if (bid && bid.params) { + return { + id: bid.params.user_id && typeof bid.params.user_id == 'string' ? bid.params.user_id : '', + // TODO: commented out because of rule violations + buyeruid: '', // localStorage.getItem('adx_profile_guid') ? localStorage.getItem('adx_profile_guid') : '', + keywords: bid.params.keywords && typeof bid.params.keywords == 'string' ? bid.params.keywords : '', + customdata: bid.params.customdata && typeof bid.params.customdata == 'string' ? bid.params.customdata : '' + }; + } +} +// Export const spec +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: BANNER, + isBidRequestValid, + buildRequests, + interpretResponse +} + +registerBidder(spec); diff --git a/modules/relevatehealthBidAdapter.md b/modules/relevatehealthBidAdapter.md new file mode 100644 index 00000000000..432e4fcec02 --- /dev/null +++ b/modules/relevatehealthBidAdapter.md @@ -0,0 +1,40 @@ +# Overview + +``` +Module Name: relevatehealth Bidder Adapter +Module Type: Bidder Adapter +Maintainer: marketingops@relevatehealth.com +``` + +# Description + +relevatehealth currently supports the BANNER type ads through prebid js + +Module that connects to relevatehealth's demand sources. + +# Banner Test Request +``` + var adUnits = [ + { + code: 'banner-ad', + mediaTypes: { + banner: { + sizes: [[160, 600]], + } + } + bids: [ + { + bidder: 'relevatehealth', + params: { + placement_id: 110011, // Required parameter + user_id: '1111111' // Required parameter + width: 160, // Optional parameter + height: 600, // Optional parameter + domain: '', // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/modules/resetdigitalBidAdapter.js b/modules/resetdigitalBidAdapter.js index 1fe4b4d750c..4f1ebafdf9c 100644 --- a/modules/resetdigitalBidAdapter.js +++ b/modules/resetdigitalBidAdapter.js @@ -1,4 +1,4 @@ -import { timestamp, deepAccess, isStr, deepClone } from '../src/utils.js'; +import { timestamp, deepAccess, isStr, deepClone, isPlainObject } from '../src/utils.js'; import { getOrigin } from '../libraries/getOrigin/index.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -92,7 +92,7 @@ export const spec = { size: '*', }); if ( - typeof floorInfo === 'object' && + isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor)) ) { diff --git a/modules/resetdigitalBidAdapter.md b/modules/resetdigitalBidAdapter.md index a368c7f5633..64b600a7cc0 100644 --- a/modules/resetdigitalBidAdapter.md +++ b/modules/resetdigitalBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: ResetDigital Bidder Adapter Module Type: Bidder Adapter -Maintainer: bruce@resetdigital.co +Maintainer: BidderSupport@resetdigital.co ``` # Description @@ -15,49 +15,58 @@ Video is supported but requires a publisher supplied renderer at this time. ## Web ``` - var adUnits = [ - { - code: 'your-div', - mediaTypes: { - banner: { - sizes: [[300,250]] - }, - - }, - bids: [ - { - bidder: "resetdigital", - params: { - pubId: "your-pub-id", - site_id: "your-site-id", - forceBid: true, - } - } - ] - } - ]; - - - var videoAdUnits = [ - { - code: 'your-div', - mediaTypes: { - video: { - playerSize: [640, 480] - }, - - }, - bids: [ - { - bidder: "resetdigital", - params: { - pubId: "your-pub-id", - site_id: "your-site-id", - forceBid: true, - } - } - ] - } - ]; +// Define the ad units for banner ads +var adUnits = [ + { + code: 'your-div', // Replace with the actual ad unit code + mediaTypes: { + banner: { + sizes: [[300, 250]] // Define the sizes for banner ads + } + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", // (required) Replace with your publisher ID + site_id: "your-site-id", // Replace with your site ID + endpoint: 'https://ads.resetsrv.com', // (optional) Endpoint URL for the ad server + forceBid: true, // Optional parameter to force the bid + zoneId: { // (optional) Zone ID parameters + placementId: "", // Optional ID used for reports + deals: "", // Optional string of deal IDs, comma-separated + test: 1 // Set to 1 to force the bidder to respond with a creative + } + } + } + ] + } +]; +// Define the ad units for video ads +var videoAdUnits = [ + { + code: 'your-div', // Replace with the actual video ad unit code + mediaTypes: { + video: { + playerSize: [640, 480] // Define the player size for video ads + } + }, + bids: [ + { + bidder: "resetdigital", + params: { + pubId: "your-pub-id", // (required) Replace with your publisher ID + site_id: "your-site-id", // Replace with your site ID + forceBid: true, // Optional parameter to force the bid + zoneId: { // (optional) Zone ID parameters + placementId: "", // Optional ID used for reports + deals: "", // Optional string of deal IDs, comma-separated + test: 1 // Set to 1 to force the bidder to respond with a creative + } + } + } + ] + } +]; ``` diff --git a/modules/revcontentBidAdapter.js b/modules/revcontentBidAdapter.js index f1d5521f780..ce04e3aa822 100644 --- a/modules/revcontentBidAdapter.js +++ b/modules/revcontentBidAdapter.js @@ -207,7 +207,7 @@ function buildImp(bid, id) { currency: 'USD', mediaType: '*', size: '*' - }).floor; + })?.floor; } else { bidfloor = deepAccess(bid, `params.bidfloor`) || 0.1; } diff --git a/modules/rewardedInterestIdSystem.js b/modules/rewardedInterestIdSystem.js new file mode 100644 index 00000000000..8cf514f372b --- /dev/null +++ b/modules/rewardedInterestIdSystem.js @@ -0,0 +1,142 @@ +/** + * This module adds rewarded interest ID to the User ID module + * The {@link module:modules/userId} module is required + * @module modules/rewardedInterestIdSystem + * @requires module:modules/userId + */ + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').IdResponse} IdResponse + */ + +/** + * @typedef RewardedInterestApi + * @property {getApiVersion} getApiVersion + * @property {getIdentityToken} getIdentityToken + */ + +/** + * Retrieves the Rewarded Interest API version. + * @callback getApiVersion + * @return {string} + */ + +/** + * Retrieves the current identity token. + * @callback getIdentityToken + * @return {Promise} + */ + +import {submodule} from '../src/hook.js'; +import {logError} from '../src/utils.js'; + +export const MODULE_NAME = 'rewardedInterestId'; +export const SOURCE = 'rewardedinterest.com'; + +/** + * Get rewarded interest API + * @function + * @returns {RewardedInterestApi|undefined} + */ +export function getRewardedInterestApi() { + if (window.__riApi && window.__riApi.getIdentityToken) { + return window.__riApi; + } +} + +/** + * Wait while rewarded interest API to be set and execute the callback function + * @param {function} callback + */ +export function watchRewardedInterestApi(callback) { + Object.defineProperties(window, { + __rewardedInterestApi: { + value: undefined, + writable: true + }, + __riApi: { + get: () => { + return window.__rewardedInterestApi; + }, + set: value => { + window.__rewardedInterestApi = value; + callback(value); + }, + configurable: true, + } + }); +} + +/** + * Get rewarded interest ID from API and pass it to the callback function + * @param {RewardedInterestApi} rewardedInterestApi + * @param {function} callback User ID callbackCompleted + */ +export function getRewardedInterestId(rewardedInterestApi, callback) { + rewardedInterestApi.getIdentityToken().then(callback).catch(error => { + callback(); + logError(`${MODULE_NAME} module: ID fetch encountered an error`, error); + }); +} + +/** + * @param {function} callback User ID callbackCompleted + */ +export function apiNotAvailable(callback) { + callback(); + logError(`${MODULE_NAME} module: Rewarded Interest API not found`); +} + +/** @type {Submodule} */ +export const rewardedInterestIdSubmodule = { + /** + * 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 {{rewardedInterestId: string}|undefined} + */ + decode(value) { + return value ? {[MODULE_NAME]: value} : undefined; + }, + + /** + * Performs action to obtain id and return a value in the callback's response argument + * @function + * @returns {IdResponse|undefined} + */ + getId() { + return { + callback: cb => { + const api = getRewardedInterestApi(); + if (api) { + getRewardedInterestId(api, cb); + } else if (document.readyState === 'complete') { + apiNotAvailable(cb); + } else { + watchRewardedInterestApi(api => getRewardedInterestId(api, cb)); + // Ensure that cb is called when API is not available + window.addEventListener('load', () => { + if (!getRewardedInterestApi()) { + apiNotAvailable(cb); + } + }) + } + }, + }; + }, + eids: { + [MODULE_NAME]: { + source: SOURCE, + atype: 3, + }, + }, +}; + +submodule('userId', rewardedInterestIdSubmodule); diff --git a/modules/rewardedInterestIdSystem.md b/modules/rewardedInterestIdSystem.md new file mode 100644 index 00000000000..8d12aa86e61 --- /dev/null +++ b/modules/rewardedInterestIdSystem.md @@ -0,0 +1,23 @@ +## Rewarded Interest User ID Submodule + +This module adds rewarded interest advertising token to the user ID module + +*Note: The storage config should be omitted + +### Prebid Params + +```javascript +pbjs.setConfig({ + userSync: { + userIds: [{ + name: 'rewardedInterestId', + }] + } +}); +``` + +## Parameter Descriptions for the `usersync` Configuration Section + +| Param under usersync.userIds[] | Scope | Type | Description | Example | +|--------------------------------|----------|--------|--------------------------|------------------------| +| name | Required | String | The name of this module. | `"rewardedInterestId"` | diff --git a/modules/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js old mode 100755 new mode 100644 index b63e31266fb..cfb0f97fafd --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -3,7 +3,6 @@ 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 {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; const BIDDER_CODE = 'richaudience'; let REFERER = ''; @@ -11,7 +10,7 @@ let REFERER = ''; export const spec = { code: BIDDER_CODE, gvlid: 108, - aliases: ['ra'], + aliases: [{code: 'ra', gvlid: 108}], supportedMediaTypes: [BANNER, VIDEO], /*** @@ -37,7 +36,6 @@ export const spec = { pid: bid.params.pid, supplyType: bid.params.supplyType, currencyCode: config.getConfig('currency.adServerCurrency'), - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 auctionId: bid.auctionId, bidId: bid.bidId, BidRequestsCount: bid.bidRequestsCount, @@ -45,7 +43,6 @@ export const spec = { bidderRequestId: bid.bidderRequestId, tagId: bid.adUnitCode, sizes: raiGetSizes(bid), - // TODO: is 'page' the right value here? referer: (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null), numIframes: (typeof bidderRequest.refererInfo.numIframes != 'undefined' ? bidderRequest.refererInfo.numIframes : null), transactionId: bid.ortb2Imp?.ext?.tid, @@ -55,12 +52,13 @@ export const spec = { videoData: raiGetVideoInfo(bid), scr_rsl: raiGetResolution(), cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), - kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), + kws: bid.params.keywords, schain: bid.schain, - gpid: raiSetPbAdSlot(bid) + gpid: raiSetPbAdSlot(bid), + dsa: setDSA(bid), + userData: deepAccess(bid, 'ortb2.user.data') }; - // TODO: is 'page' the right value here? REFERER = (typeof bidderRequest.refererInfo.page != 'undefined' ? encodeURIComponent(bidderRequest.refererInfo.page) : null) payload.gdpr_consent = ''; @@ -119,7 +117,9 @@ export const spec = { netRevenue: response.netRevenue, currency: response.currency, ttl: response.ttl, - meta: response.adomain, + meta: { + advertiserDomains: [response.adomain[0]] + }, dealId: response.dealId }; @@ -154,7 +154,7 @@ export const spec = { * * @param {syncOptions} Publisher prebid configuration * @param {serverResponses} Response from the server - * @param {gdprConsent} GPDR consent object + * @param {gdprConsent} GDPR consent object * @returns {Array} */ getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { @@ -376,3 +376,8 @@ function raiGetTimeoutURL(data) { } return url } + +function setDSA(bid) { + let dsa = bid?.ortb2?.regs?.ext?.dsa ? bid?.ortb2?.regs?.ext?.dsa : null; + return dsa; +} diff --git a/modules/richaudienceBidAdapter.md b/modules/richaudienceBidAdapter.md index f888117b166..35298b8421d 100644 --- a/modules/richaudienceBidAdapter.md +++ b/modules/richaudienceBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: Rich Audience Bidder Adapter Module Type: Bidder Adapter -Maintainer: cert@richaudience.com +Maintainer: integrations@richaudience.com ``` # Description diff --git a/modules/rasBidAdapter.js b/modules/ringieraxelspringerBidAdapter.js similarity index 86% rename from modules/rasBidAdapter.js rename to modules/ringieraxelspringerBidAdapter.js index 74abd0fb4a1..14033c1247f 100644 --- a/modules/rasBidAdapter.js +++ b/modules/ringieraxelspringerBidAdapter.js @@ -7,8 +7,9 @@ import { } from '../src/utils.js'; import { getAllOrtbKeywords } from '../libraries/keywords/keywords.js'; import { getAdUnitSizes } from '../libraries/sizeUtils/sizeUtils.js'; +import { BO_CSR_ONET } from '../libraries/paapiTools/buyerOrigins.js'; -const BIDDER_CODE = 'ras'; +const BIDDER_CODE = 'ringieraxelspringer'; const VERSION = '1.0'; const getEndpoint = (network) => { @@ -106,38 +107,66 @@ function parseOrtbResponse(ad) { return false; } - const { image, Image, title, url, Headline, Thirdpartyclicktracker, imp, impression, impression1, impressionJs1 } = ad.data.fields; + const { image, Image, title, url, Headline, Thirdpartyclicktracker, thirdPartyClickTracker2, imp, impression, impression1, impressionJs1, partner_logo: partnerLogo, adInfo, body } = ad.data.fields; const { dsaurl, height, width, adclick } = ad.data.meta; const emsLink = ad.ems_link; const link = adclick + (url || Thirdpartyclicktracker); const eventtrackers = prepareEventtrackers(emsLink, imp, impression, impression1, impressionJs1); + const clicktrackers = thirdPartyClickTracker2 ? [thirdPartyClickTracker2] : []; + const ortb = { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: body || '', + type: 2 + }, + }, + { + id: 1, + data: { + value: adInfo || '', + // Body2 type + type: 10 + }, + }, + { + id: 3, img: { - url: image || Image || '', + type: 1, + url: partnerLogo || '', w: width, h: height } }, { id: 4, - title: { - text: title || Headline || '' + img: { + type: 3, + url: image || Image || '', + w: width, + h: height } }, { - id: 3, + id: 5, data: { value: deepAccess(ad, 'data.meta.advertiser_name', null), type: 1 } - } + }, + { + id: 6, + title: { + text: title || Headline || '' + } + }, ], link: { - url: link + url: link, + clicktrackers }, eventtrackers }; @@ -154,7 +183,7 @@ function parseNativeResponse(ad) { return false; } - const { image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker } = ad.data.fields; + const { image, Image, title, leadtext, url, Calltoaction, Body, Headline, Thirdpartyclicktracker, adInfo, partner_logo: partnerLogo } = ad.data.fields; const { dsaurl, height, width, adclick } = ad.data.meta; const link = adclick + (url || Thirdpartyclicktracker); const nativeResponse = { @@ -165,10 +194,15 @@ function parseNativeResponse(ad) { width, height }, - + icon: { + url: partnerLogo || '', + width, + height + }, clickUrl: link, cta: Calltoaction || '', body: leadtext || Body || '', + body2: adInfo || '', sponsoredBy: deepAccess(ad, 'data.meta.advertiser_name', null) || '', ortb: parseOrtbResponse(ad) }; @@ -192,7 +226,7 @@ const buildBid = (ad, mediaType) => { creativeId: ad.adid ? parseInt(ad.adid.split(',')[2], 10) : 0, netRevenue: true, currency: ad.currency || 'USD', - dealId: null, + dealId: ad.prebid_deal || null, actgMatch: ad.actg_match || 0, meta: { mediaType: BANNER }, mediaType: BANNER, @@ -243,6 +277,8 @@ const getSlots = (bidRequests) => { queryString += `&cre_format${i}=native`; } + queryString += `&kvhb_format${i}=${creFormat === 'native' ? 'native' : 'banner'}`; + if (sizes) { queryString += `&iusizes${i}=${encodeURIComponent(sizes)}`; } @@ -279,9 +315,9 @@ const parseAuctionConfigs = (serverResponse, bidRequest) => { auctionConfigs.push({ 'bidId': bid.bidId, 'config': { - 'seller': 'https://csr.onet.pl', - 'decisionLogicUrl': `https://csr.onet.pl/${encodeURIComponent(bid.params.network)}/v1/protected-audience-api/decision-logic.js`, - 'interestGroupBuyers': ['https://csr.onet.pl'], + 'seller': BO_CSR_ONET, + 'decisionLogicUrl': `${BO_CSR_ONET}/${encodeURIComponent(bid.params.network)}/v1/protected-audience-api/decision-logic.js`, + 'interestGroupBuyers': [ BO_CSR_ONET ], 'auctionSignals': { 'params': bid.params, 'sizes': bid.sizes, @@ -329,7 +365,7 @@ export const spec = { const slotsQuery = getSlots(bidRequests); const contextQuery = getContextParams(bidRequests, bidderRequest); const gdprQuery = getGdprParams(bidderRequest); - const fledgeEligible = Boolean(bidderRequest && bidderRequest.fledgeEnabled); + const fledgeEligible = Boolean(bidderRequest?.paapi?.enabled); const network = bidRequests[0].params.network; const bidIds = bidRequests.map((bid) => ({ slot: bid.params.slot, @@ -357,7 +393,7 @@ export const spec = { if (fledgeAuctionConfigs) { // Return a tuple of bids and auctionConfigs. It is possible that bids could be null. - return {bids, fledgeAuctionConfigs}; + return {bids, paapi: fledgeAuctionConfigs}; } else { return bids; } diff --git a/modules/rasBidAdapter.md b/modules/ringieraxelspringerBidAdapter.md similarity index 88% rename from modules/rasBidAdapter.md rename to modules/ringieraxelspringerBidAdapter.md index cf169fedb63..b3a716f9f56 100644 --- a/modules/rasBidAdapter.md +++ b/modules/ringieraxelspringerBidAdapter.md @@ -21,7 +21,7 @@ var adUnits = [{ } }, bids: [{ - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { network: '4178463', site: 'test', @@ -36,11 +36,11 @@ var adUnits = [{ | Name | Scope | Type | Description | Example | |------------------------------|----------|----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|---------------------------------------------------------------------------------------------| -| network | required | String | Specific identifier provided by RAS | `"4178463"` | -| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by RAS | `"example_com"` | +| network | required | String | Specific identifier provided by Ringier Axel Springer | `"4178463"` | +| site | required | String | Specific identifier name (case-insensitive) that is associated with this ad unit and provided by Ringier Axel Springer | `"example_com"` | | area | required | String | Ad unit category name; only case-insensitive alphanumeric with underscores and hyphens are allowed | `"sport"` | -| slot | required | String | Ad unit placement name (case-insensitive) provided by RAS | `"slot"` | -| slotSequence | optional | Number | Ad unit sequence position provided by RAS | `1` | +| slot | required | String | Ad unit placement name (case-insensitive) provided by Ringier Axel Springer | `"slot"` | +| slotSequence | optional | Number | Ad unit sequence position provided by Ringier Axel Springer | `1` | | pageContext | optional | Object | Web page context data | `{}` | | pageContext.dr | optional | String | Document referrer URL address | `"https://example.com/"` | | pageContext.du | optional | String | Document URL address | `"https://example.com/sport/football/article.html?id=932016a5-02fc-4d5c-b643-fafc2f270f06"` | diff --git a/modules/riseBidAdapter.js b/modules/riseBidAdapter.js index b176ab08aaf..236c048982a 100644 --- a/modules/riseBidAdapter.js +++ b/modules/riseBidAdapter.js @@ -2,18 +2,17 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'rise'; @@ -21,15 +20,11 @@ const ADAPTER_VERSION = '6.0.0'; const TTL = 360; const DEFAULT_CURRENCY = 'USD'; const DEFAULT_GVLID = 1043; -const DEFAULT_SELLER_ENDPOINT = 'https://hb.yellowblue.io/'; +const BASE_URL = 'https://hb.yellowblue.io/'; const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -59,48 +54,23 @@ export const spec = { // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; - const rtbDomain = generalObject.params.rtbDomain; + const rtbDomain = generalObject.params.rtbDomain || BASE_URL; combinedRequestsObject.params = generateGeneralParams(generalObject, bidderRequest); combinedRequestsObject.bids = generateBidsParams(validBidRequests, bidderRequest); return { method: 'POST', - url: getEndpoint(testMode, rtbDomain), + url: getEndpoint(testMode, rtbDomain, MODES), data: combinedRequestsObject } }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -110,20 +80,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -141,354 +111,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @param mediaType {string} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(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 !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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && contains(bidders, bidderCode); -} - -/** - * Get the seller endpoint - * @param testMode {boolean} - * @param rtbDomain {string} - * @returns {string} - */ -function getEndpoint(testMode, rtbDomain) { - const SELLER_ENDPOINT = rtbDomain ? `https://${rtbDomain}/` : DEFAULT_SELLER_ENDPOINT; - 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'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - transactionId: bid.ortb2Imp?.ext?.tid, - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest && bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - // TODO: is 'ref' the right value here? - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - // TODO: does the fallback make sense here? - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/rtbhouseBidAdapter.js b/modules/rtbhouseBidAdapter.js index 1cd97696770..74a4df14f6f 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -5,6 +5,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {includes} from '../src/polyfill.js'; import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; import {config} from '../src/config.js'; +import { interpretNativeBid, OPENRTB } from '../libraries/precisoUtils/bidNativeUtils.js'; const BIDDER_CODE = 'rtbhouse'; const REGIONS = ['prebid-eu', 'prebid-us', 'prebid-asia']; @@ -24,29 +25,6 @@ const DSA_ATTRIBUTES = [ { name: 'datatopub', 'min': 0, 'max': 2 } ]; -// Codes defined by OpenRTB Native Ads 1.1 specification -export const OPENRTB = { - NATIVE: { - 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, - }, - } -}; - export const spec = { code: BIDDER_CODE, supportedMediaTypes: SUPPORTED_MEDIA_TYPES, @@ -114,11 +92,12 @@ export const spec = { let computedEndpointUrl = ENDPOINT_URL; - if (bidderRequest.fledgeEnabled) { - const fledgeConfig = config.getConfig('fledgeConfig') || { + if (bidderRequest.paapi?.enabled) { + const fromConfig = config.getConfig('paapiConfig') || config.getConfig('fledgeConfig') || { sellerTimeout: 500 }; + const fledgeConfig = { seller: FLEDGE_SELLER_URL, decisionLogicUrl: FLEDGE_DECISION_LOGIC_URL, - sellerTimeout: 500 + ...fromConfig }; mergeDeep(request, { ext: { fledge_config: fledgeConfig } }); computedEndpointUrl = FLEDGE_ENDPOINT_URL; @@ -165,7 +144,6 @@ export const spec = { interpretResponse: function (serverResponse, originalRequest) { let bids; - const fledgeInterestGroupBuyers = config.getConfig('fledgeConfig.interestGroupBuyers') || []; const responseBody = serverResponse.body; let fledgeAuctionConfigs = null; @@ -173,24 +151,35 @@ export const spec = { // we have fledge response // mimic the original response ([{},...]) bids = this.interpretOrtbResponse({ body: responseBody.seatbid[0]?.bid }, originalRequest); - - const seller = responseBody.ext.seller; - const decisionLogicUrl = responseBody.ext.decisionLogicUrl; - const sellerTimeout = 'sellerTimeout' in responseBody.ext ? { sellerTimeout: responseBody.ext.sellerTimeout } : {}; + const paapiAdapterConfig = config.getConfig('paapiConfig') || config.getConfig('fledgeConfig') || {}; + const fledgeInterestGroupBuyers = paapiAdapterConfig.interestGroupBuyers || []; + // values from the response.ext are the most important + const { + decisionLogicUrl = paapiAdapterConfig.decisionLogicUrl || paapiAdapterConfig.decisionLogicURL || + FLEDGE_DECISION_LOGIC_URL, + seller = paapiAdapterConfig.seller || FLEDGE_SELLER_URL, + sellerTimeout = 500 + } = responseBody.ext; + + const fledgeConfig = { + seller, + decisionLogicUrl, + decisionLogicURL: decisionLogicUrl, + sellerTimeout + }; + // fledgeConfig settings are more important; other paapiAdapterConfig settings are facultative + mergeDeep(fledgeConfig, paapiAdapterConfig, fledgeConfig); responseBody.ext.igbid.forEach((igbid) => { - const perBuyerSignals = {}; + const perBuyerSignals = {...fledgeConfig.perBuyerSignals}; // may come from paapiAdapterConfig igbid.igbuyer.forEach(buyerItem => { perBuyerSignals[buyerItem.igdomain] = buyerItem.buyersignal }); fledgeAuctionConfigs = fledgeAuctionConfigs || {}; - fledgeAuctionConfigs[igbid.impid] = mergeDeep( + fledgeAuctionConfigs[igbid.impid] = mergeDeep({}, fledgeConfig, { - seller, - decisionLogicUrl, - interestGroupBuyers: [...fledgeInterestGroupBuyers, ...Object.keys(perBuyerSignals)], + interestGroupBuyers: [...new Set([...fledgeInterestGroupBuyers, ...Object.keys(perBuyerSignals)])], perBuyerSignals, - }, - sellerTimeout + } ); }); } else { @@ -209,7 +198,7 @@ export const spec = { logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids; @@ -226,7 +215,7 @@ function applyFloor(slot) { 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); + floors.push(slot.getFloor({ currency: DEFAULT_CURRENCY_ARR[0], mediaType: type, size: slot.sizes || '*' })?.floor); } }); } @@ -250,7 +239,7 @@ function mapImpression(slot, bidderRequest) { imp.bidfloor = bidfloor; } - if (bidderRequest.fledgeEnabled) { + if (bidderRequest.paapi?.enabled) { imp.ext = imp.ext || {}; imp.ext.ae = slot?.ortb2Imp?.ext?.ae } else { @@ -478,71 +467,6 @@ function interpretBannerBid(serverBid) { } } -/** - * @param {object} serverBid Bid by OpenRTB 2.5 §4.2.3 - * @returns {object} Prebid native bidObject - */ -function interpretNativeBid(serverBid) { - return { - requestId: serverBid.impid, - mediaType: NATIVE, - cpm: serverBid.price, - creativeId: serverBid.adid, - width: 1, - height: 1, - ttl: TTL, - meta: { - advertiserDomains: serverBid.adomain - }, - netRevenue: true, - currency: 'USD', - native: interpretNativeAd(serverBid.adm), - } -} - -/** - * @param {string} adm JSON-encoded Request by OpenRTB Native Ads 1.1 §4.1 - * @returns {object} Prebid bidObject.native - */ -function interpretNativeAd(adm) { - const native = JSON.parse(adm).native; - const result = { - clickUrl: encodeURI(native.link.url), - impressionTrackers: native.imptrackers - }; - native.assets.forEach(asset => { - switch (asset.id) { - case OPENRTB.NATIVE.ASSET_ID.TITLE: - result.title = asset.title.text; - break; - case OPENRTB.NATIVE.ASSET_ID.IMAGE: - result.image = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.ICON: - result.icon = { - url: encodeURI(asset.img.url), - width: asset.img.w, - height: asset.img.h - }; - break; - case OPENRTB.NATIVE.ASSET_ID.BODY: - result.body = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.SPONSORED: - result.sponsoredBy = asset.data.value; - break; - case OPENRTB.NATIVE.ASSET_ID.CTA: - result.cta = asset.data.value; - break; - } - }); - return result; -} - /** * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/main/extensions/community_extensions/dsa_transparency.md * diff --git a/modules/rtbhouseBidAdapter.md b/modules/rtbhouseBidAdapter.md index 338ba6b4df4..7fcae1299b2 100644 --- a/modules/rtbhouseBidAdapter.md +++ b/modules/rtbhouseBidAdapter.md @@ -69,7 +69,7 @@ Please reach out to pmp@rtbhouse.com to receive your own # Protected Audience API (FLEDGE) support There’s an option to receive demand for Protected Audience API (FLEDGE/PAAPI) ads using RTB House bid adapter. -Prebid’s [fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +Prebid’s [paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module and Google Ad Manager is currently required. The following steps should be taken to setup Protected Audience for RTB House: @@ -77,15 +77,15 @@ The following steps should be taken to setup Protected Audience for RTB House: 1. Reach out to your RTB House representative for setup coordination. 2. Build and enable FLEDGE module as described in -[fledgeForGpt](https://docs.prebid.org/dev-docs/modules/fledgeForGpt.html) +[paapiForGpt](https://docs.prebid.org/dev-docs/modules/paapiForGpt.html) module documentation. a. Make sure to enable RTB House bidder to participate in FLEDGE. If there are any other bidders to be allowed for that, add them to the **bidders** array: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true } }); ``` @@ -93,15 +93,15 @@ module documentation. b. If you as a publisher have your own [decisionLogicUrl](https://github.com/WICG/turtledove/blob/main/FLEDGE.md#21-initiating-an-on-device-auction) you may utilize it by setting up a dedicated `fledgeConfig` object: ```javascript - pbjs.setBidderConfig({ - bidders: ["rtbhouse"], - config: { - fledgeEnabled: true, - fledgeConfig: { - seller: 'https://seller.domain', - decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', - sellerTimeout: 100 - } + pbjs.setConfig({ + paapi: { + bidders: ["rtbhouse"], + enabled: true + }, + fledgeConfig: { + seller: 'https://seller.domain', + decisionLogicUrl: 'https://seller.domain/decisionLogicFile.js', + sellerTimeout: 100 } }); ``` diff --git a/modules/rtdModule/index.js b/modules/rtdModule/index.js index 0c654fc28b0..2ebc334b29e 100644 --- a/modules/rtdModule/index.js +++ b/modules/rtdModule/index.js @@ -188,10 +188,12 @@ let _dataProviders = []; let _userConsent; /** - * Register a RTD submodule. + * Register a Real-Time Data (RTD) submodule. * - * @param {RtdSubmodule} submodule - * @returns {function()} a de-registration function that will unregister the module when called. + * @param {Object} submodule The RTD submodule to register. + * @param {string} submodule.name The name of the RTD submodule. + * @param {number} [submodule.gvlid] The Global Vendor List ID (GVLID) of the RTD submodule. + * @returns {function(): void} A de-registration function that will unregister the module when called. */ export function attachRealTimeDataProvider(submodule) { registeredSubModules.push(submodule); @@ -316,10 +318,8 @@ export const setBidRequestsData = timedAuctionHook('rtd', function setBidRequest relevantSubModules.forEach(sm => { const fpdGuard = guardOrtb2Fragments(reqBidsConfigObj.ortb2Fragments || {}, activityParams(MODULE_TYPE_RTD, sm.name)); verifiers.push(fpdGuard.verify); - sm.getBidRequestData({ - ...reqBidsConfigObj, - ortb2Fragments: fpdGuard.obj - }, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) + reqBidsConfigObj.ortb2Fragments = fpdGuard.obj; + sm.getBidRequestData(reqBidsConfigObj, onGetBidRequestDataCallback.bind(sm), sm.config, _userConsent) }); function onGetBidRequestDataCallback() { diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9e47807bdc0..411e194b1ee 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -3,7 +3,6 @@ import { pbsExtensions } from '../libraries/pbsExtensions/pbsExtensions.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { find } from '../src/polyfill.js'; import { getGlobal } from '../src/prebidGlobal.js'; import { Renderer } from '../src/Renderer.js'; import { @@ -19,9 +18,11 @@ import { mergeDeep, parseSizesInput, pick, - _each + _each, + isPlainObject } from '../src/utils.js'; import {getAllOrtbKeywords} from '../libraries/keywords/keywords.js'; +import {getUserSyncParams} from '../libraries/userSyncUtils/userSyncUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -41,6 +42,8 @@ config.getConfig('rubicon', config => { const GVLID = 52; +let impIdMap = {}; + var sizeMap = { 1: '468x60', 2: '728x90', @@ -147,7 +150,13 @@ var sizeMap = { 580: '505x656', 622: '192x160', 632: '1200x450', - 634: '340x450' + 634: '340x450', + 680: '970x570', + 682: '300x240', + 684: '970x550', + 686: '300x210', + 688: '300x220', + 690: '970x170' }; _each(sizeMap, (item, key) => sizeMap[item] = key); @@ -211,6 +220,9 @@ export const converter = ortbConverter({ setBidFloors(bidRequest, imp); + // ensure unique imp IDs for twin adunits + imp.id = impIdMap[imp.id] ? imp.id + impIdMap[imp.id]++ : (impIdMap[imp.id] = 2, imp.id); + return imp; }, bidResponse(buildBidResponse, bid, context) { @@ -219,9 +231,9 @@ export const converter = ortbConverter({ const {bidRequest} = context; let [parseSizeWidth, parseSizeHeight] = bidRequest.mediaTypes.video?.context === 'outstream' ? parseSizes(bidRequest, VIDEO) : [undefined, undefined]; - - bidResponse.width = bid.w || parseSizeWidth || bidResponse.playerWidth; - bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight; + // 0 by default to avoid undefined size + bidResponse.width = bid.w || parseSizeWidth || bidResponse.playerWidth || 0; + bidResponse.height = bid.h || parseSizeHeight || bidResponse.playerHeight || 0; if (bidResponse.mediaType === VIDEO && bidRequest.mediaTypes.video.context === 'outstream') { bidResponse.renderer = outstreamRenderer(bidResponse); @@ -301,6 +313,7 @@ export const spec = { if (filteredRequests && filteredRequests.length) { const data = converter.toORTB({bidRequests: filteredRequests, bidderRequest}); + resetImpIdMap(); filteredHttpRequest.push({ method: 'POST', @@ -486,6 +499,8 @@ export const spec = { 'x_imp.ext.tid': bidRequest.ortb2Imp?.ext?.tid, 'l_pb_bid_id': bidRequest.bidId, 'o_cdep': bidRequest.ortb2?.device?.ext?.cdep, + 'ip': bidRequest.ortb2?.device?.ip, + 'ipv6': bidRequest.ortb2?.device?.ipv6, 'p_screen_res': _getScreenResolution(), 'tk_user_key': params.userId, 'p_geo.latitude': isNaN(parseFloat(latitude)) ? undefined : parseFloat(latitude).toFixed(4), @@ -506,7 +521,7 @@ export const spec = { } catch (e) { logError('Rubicon: getFloor threw an error: ', e); } - data['rp_hard_floor'] = typeof floorInfo === 'object' && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; + data['rp_hard_floor'] = isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseInt(floorInfo.floor)) ? floorInfo.floor : undefined; } // Send multiformat data if requested @@ -529,42 +544,47 @@ export const spec = { if (bidRequest?.ortb2Imp?.ext?.ae) { data['o_ae'] = 1; } + // If the bid request contains a 'mobile' property under 'ortb2.site', add it to 'data' as 'p_site.mobile'. + if (typeof bidRequest?.ortb2?.site?.mobile === 'number') { + data['p_site.mobile'] = bidRequest.ortb2.site.mobile + } addDesiredSegtaxes(bidderRequest, data); // loop through userIds and add to request - if (bidRequest.userIdAsEids) { - bidRequest.userIdAsEids.forEach(eid => { + if (bidRequest?.ortb2?.user?.ext?.eids) { + bidRequest.ortb2.user.ext.eids.forEach(({ source, uids = [], inserter, matcher, mm, ext = {} }) => { 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 - // if rubicon drop ^ - const id = eid.source === 'rubiconproject.com' ? eid.uids[0].id : `${eid.uids[0].id}^${eid.uids[0].atype || ''}` - data[`eid_${eid.source}`] = id; - } - // send AE "ppuid" signal if exists, and hasn't already been sent + // Ensure there is at least one valid UID in the 'uids' array + const uidData = uids[0]; + if (!uidData) return; // Skip processing if no valid UID exists + + // Function to build the EID value in the required format + const buildEidValue = (uidData) => [ + uidData.id, // uid: The user ID + uidData.atype || '', + '', // third: Always empty, as specified in the requirement + inserter || '', + matcher || '', + mm || '', + uidData?.ext?.rtipartner || '' + ].join('^'); // Return a single string formatted with '^' delimiter + + const eidValue = buildEidValue(uidData); // Build the EID value string + + // Store the constructed EID value for the given source + data[`eid_${source}`] = eidValue; + + // Handle the "ppuid" signal, ensuring it is set only once 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; + // Search for a UID with the 'stype' field equal to 'ppuid' in its extension + const ppId = uids.find(uid => uid.ext?.stype === 'ppuid'); + if (ppId?.id) { + data['ppuid'] = ppId.id; // Store the ppuid if found } } } catch (e) { - logWarn('Rubicon: error reading eid:', eid, e); + // Log any errors encountered during processing + logWarn('Rubicon: error reading eid:', { source, uids }, e); } }); } @@ -704,6 +724,10 @@ export const spec = { bid.meta.advertiserDomains = Array.isArray(ad.adomain) ? ad.adomain : [ad.adomain]; } + if (ad.emulated_format) { + bid.meta.mediaType = ad.emulated_format; + } + if (ad.creative_type === VIDEO) { bid.width = associatedBidRequest.params.video.playerWidth; bid.height = associatedBidRequest.params.video.playerHeight; @@ -736,7 +760,7 @@ export const spec = { }); if (fledgeAuctionConfigs) { - return { bids, fledgeAuctionConfigs }; + return { bids, paapi: fledgeAuctionConfigs }; } else { return bids; } @@ -744,26 +768,7 @@ export const spec = { getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent, gppConsent) { if (!hasSynced && syncOptions.iframeEnabled) { // data is only assigned if params are available to pass to syncEndpoint - let params = {}; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies === 'boolean') { - params['gdpr'] = Number(gdprConsent.gdprApplies); - } - if (typeof gdprConsent.consentString === 'string') { - params['gdpr_consent'] = gdprConsent.consentString; - } - } - - if (uspConsent) { - params['us_privacy'] = encodeURIComponent(uspConsent); - } - - if (gppConsent?.gppString) { - params['gpp'] = gppConsent.gppString; - params['gpp_sid'] = gppConsent.applicableSections?.toString(); - } - + let params = getUserSyncParams(gdprConsent, uspConsent, gppConsent); params = Object.keys(params).length ? `?${formatQS(params)}` : ''; hasSynced = true; @@ -842,7 +847,7 @@ function renderBid(bid) { height: bid.height, vastUrl: bid.vastUrl, placement: { - attachTo: adUnitElement, + attachTo: `#${bid.adUnitCode}`, align: config.align, position: config.position }, @@ -1170,6 +1175,7 @@ function bidType(bid, log = false) { } export const resetRubiConf = () => rubiConf = {}; +export const resetImpIdMap = () => impIdMap = {}; export function masSizeOrdering(sizes) { const MAS_SIZE_PRIORITY = [15, 2, 9]; @@ -1205,7 +1211,7 @@ export function determineRubiconVideoSizeId(bid) { } /** - * @param {PrebidConfig} config + * @param {Object} config * @returns {{ranges: {ranges: Object[]}}} */ export function getPriceGranularity(config) { @@ -1254,7 +1260,6 @@ export function hasValidVideoParams(bid) { /** * Make sure the required params are present * @param {Object} schain - * @param {boolean} */ export function hasValidSupplyChainParams(schain) { let isValid = false; diff --git a/modules/s2sTesting.js b/modules/s2sTesting.js index 8e9628c8810..4c78b62d710 100644 --- a/modules/s2sTesting.js +++ b/modules/s2sTesting.js @@ -44,7 +44,7 @@ s2sTesting.getSourceBidderMap = function(adUnits = [], allS2SBidders = []) { /** * @function calculateBidSources determines the source for each s2s bidder based on bidderControl weightings. these can be overridden at the adUnit level - * @param s2sConfigs server-to-server configuration + * @param s2sConfig server-to-server configuration */ s2sTesting.calculateBidSources = function(s2sConfig = {}) { // calculate bid source (server/client) for each s2s bidder diff --git a/modules/saambaaBidAdapter.js b/modules/saambaaBidAdapter.js index da6e7028abe..3e33496b7d9 100644 --- a/modules/saambaaBidAdapter.js +++ b/modules/saambaaBidAdapter.js @@ -1,419 +1,3 @@ -// TODO: this adapter appears to have no tests - -import {deepAccess, generateUUID, isEmpty, isFn, parseSizesInput, parseUrl} from '../src/utils.js'; -import {config} from '../src/config.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {find, includes} from '../src/polyfill.js'; - -const ADAPTER_VERSION = '1.0'; -const BIDDER_CODE = 'saambaa'; - -export const VIDEO_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const BANNER_ENDPOINT = 'https://nep.advangelists.com/xp/get?pubid='; -export const OUTSTREAM_SRC = 'https://player-cdn.beachfrontmedia.com/playerapi/loader/outstream.js'; -export const VIDEO_TARGETING = ['mimes', 'playbackmethod', 'maxduration', 'skip', 'playerSize', 'context']; -export const DEFAULT_MIMES = ['video/mp4', 'application/javascript']; - -let pubid = ''; - -export const spec = { - code: BIDDER_CODE, - supportedMediaTypes: [BANNER, VIDEO], - - isBidRequestValid(bidRequest) { - if (typeof bidRequest != 'undefined') { - if (bidRequest.bidder !== BIDDER_CODE && typeof bidRequest.params === 'undefined') { return false; } - if (bidRequest === '' || bidRequest.params.placement === '' || bidRequest.params.pubid === '') { return false; } - return true; - } else { return false; } - }, - - buildRequests(bids, bidderRequest) { - let requests = []; - let videoBids = bids.filter(bid => isVideoBidValid(bid)); - let bannerBids = bids.filter(bid => isBannerBidValid(bid)); - videoBids.forEach(bid => { - pubid = getVideoBidParam(bid, 'pubid'); - requests.push({ - method: 'POST', - url: VIDEO_ENDPOINT + pubid, - data: createVideoRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - - bannerBids.forEach(bid => { - pubid = getBannerBidParam(bid, 'pubid'); - - requests.push({ - method: 'POST', - url: BANNER_ENDPOINT + pubid, - data: createBannerRequestData(bid, bidderRequest), - bidRequest: bid - }); - }); - return requests; - }, - - interpretResponse(serverResponse, {bidRequest}) { - let response = serverResponse.body; - if (response !== null && isEmpty(response) == false) { - if (isVideoBid(bidRequest)) { - let bidResponse = { - requestId: response.id, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: VIDEO, - netRevenue: true - } - - if (response.seatbid[0].bid[0].adm) { - bidResponse.vastXml = response.seatbid[0].bid[0].adm; - bidResponse.adResponse = { - content: response.seatbid[0].bid[0].adm - }; - } else { - bidResponse.vastUrl = response.seatbid[0].bid[0].nurl; - } - - return bidResponse; - } else { - return { - requestId: response.id, - bidderCode: BIDDER_CODE, - cpm: response.seatbid[0].bid[0].price, - width: response.seatbid[0].bid[0].w, - height: response.seatbid[0].bid[0].h, - ad: response.seatbid[0].bid[0].adm, - ttl: response.seatbid[0].bid[0].ttl || 60, - creativeId: response.seatbid[0].bid[0].crid, - currency: response.cur, - meta: { 'advertiserDomains': response.seatbid[0].bid[0].adomain }, - mediaType: BANNER, - netRevenue: true - } - } - } - } -}; - -function isBannerBid(bid) { - return deepAccess(bid, 'mediaTypes.banner') || !isVideoBid(bid); -} - -function isVideoBid(bid) { - return deepAccess(bid, 'mediaTypes.video'); -} - -function getBannerBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'banner', size: '*' }) : {}; - return floorInfo.floor || getBannerBidParam(bid, 'bidfloor'); -} - -function getVideoBidFloor(bid) { - let floorInfo = isFn(bid.getFloor) ? bid.getFloor({ currency: 'USD', mediaType: 'video', size: '*' }) : {}; - return floorInfo.floor || getVideoBidParam(bid, 'bidfloor'); -} - -function isVideoBidValid(bid) { - return isVideoBid(bid) && getVideoBidParam(bid, 'pubid') && getVideoBidParam(bid, 'placement'); -} - -function isBannerBidValid(bid) { - return isBannerBid(bid) && getBannerBidParam(bid, 'pubid') && getBannerBidParam(bid, 'placement'); -} - -function getVideoBidParam(bid, key) { - return deepAccess(bid, 'params.video.' + key) || deepAccess(bid, 'params.' + key); -} - -function getBannerBidParam(bid, key) { - return deepAccess(bid, 'params.banner.' + key) || deepAccess(bid, 'params.' + key); -} - -function isMobile() { - return (/(ios|ipod|ipad|iphone|android)/i).test(navigator.userAgent); -} - -function isConnectedTV() { - return (/(smart[-]?tv|hbbtv|appletv|googletv|hdmi|netcast\.tv|viera|nettv|roku|\bdtv\b|sonydtv|inettvbrowser|\btv\b)/i).test(navigator.userAgent); -} - -function getDoNotTrack() { - return navigator.doNotTrack === '1' || window.doNotTrack === '1' || navigator.msDoNoTrack === '1' || navigator.doNotTrack === 'yes'; -} - -function findAndFillParam(o, key, value) { - try { - if (typeof value === 'function') { - o[key] = value(); - } else { - o[key] = value; - } - } catch (ex) {} -} - -function getOsVersion() { - let clientStrings = [ - { s: 'Android', r: /Android/ }, - { s: 'iOS', r: /(iPhone|iPad|iPod)/ }, - { s: 'Mac OS X', r: /Mac OS X/ }, - { s: 'Mac OS', r: /(MacPPC|MacIntel|Mac_PowerPC|Macintosh)/ }, - { s: 'Linux', r: /(Linux|X11)/ }, - { s: 'Windows 10', r: /(Windows 10.0|Windows NT 10.0)/ }, - { s: 'Windows 8.1', r: /(Windows 8.1|Windows NT 6.3)/ }, - { s: 'Windows 8', r: /(Windows 8|Windows NT 6.2)/ }, - { s: 'Windows 7', r: /(Windows 7|Windows NT 6.1)/ }, - { s: 'Windows Vista', r: /Windows NT 6.0/ }, - { s: 'Windows Server 2003', r: /Windows NT 5.2/ }, - { s: 'Windows XP', r: /(Windows NT 5.1|Windows XP)/ }, - { s: 'UNIX', r: /UNIX/ }, - { s: 'Search Bot', r: /(nuhk|Googlebot|Yammybot|Openbot|Slurp|MSNBot|Ask Jeeves\/Teoma|ia_archiver)/ } - ]; - let cs = find(clientStrings, cs => cs.r.test(navigator.userAgent)); - return cs ? cs.s : 'unknown'; -} - -function getFirstSize(sizes) { - return (sizes && sizes.length) ? sizes[0] : { w: undefined, h: undefined }; -} - -function parseSizes(sizes) { - return parseSizesInput(sizes).map(size => { - let [ width, height ] = size.split('x'); - return { - w: parseInt(width, 10) || undefined, - h: parseInt(height, 10) || undefined - }; - }); -} - -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -function getTopWindowReferrer() { - try { - return window.top.document.referrer; - } catch (e) { - return ''; - } -} - -function getVideoTargetingParams(bid) { - const result = {}; - const excludeProps = ['playerSize', 'context', 'w', 'h']; - Object.keys(Object(bid.mediaTypes.video)) - .filter(key => !includes(excludeProps, key)) - .forEach(key => { - result[ key ] = bid.mediaTypes.video[ key ]; - }); - Object.keys(Object(bid.params.video)) - .filter(key => includes(VIDEO_TARGETING, key)) - .forEach(key => { - result[ key ] = bid.params.video[ key ]; - }); - return result; -} - -function createVideoRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - let paramSize = getVideoBidParam(bid, 'size'); - let sizes = []; - let coppa = config.getConfig('coppa'); - - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getVideoSizes(bid); - } - const firstSize = getFirstSize(sizes); - let floor = (getVideoBidFloor(bid) == null || typeof getVideoBidFloor(bid) == 'undefined') ? 0.5 : getVideoBidFloor(bid); - let video = getVideoTargetingParams(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1, - 'os': getOsVersion() - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getVideoBidParam(bid, 'placement'); - - for (let j = 0; j < sizes.length; j++) { - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'video': Object.assign({ - 'id': generateUUID(), - 'pos': 0, - 'w': firstSize.w, - 'h': firstSize.h, - 'mimes': DEFAULT_MIMES - }, video) - - }); - } - if (coppa) { - o.regs.ext = {'coppa': 1}; - } - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} - -function getTopWindowLocation(bidderRequest) { - return parseUrl(bidderRequest?.refererInfo?.page || '', { decodeSearchAsString: true }); -} - -function createBannerRequestData(bid, bidderRequest) { - let topLocation = getTopWindowLocation(bidderRequest); - let topReferrer = getTopWindowReferrer(); - - // if size is explicitly given via adapter params - - let paramSize = getBannerBidParam(bid, 'size'); - let sizes = []; - let coppa = config.getConfig('coppa'); - if (typeof paramSize !== 'undefined' && paramSize != '') { - sizes = parseSizes(paramSize); - } else { - sizes = getBannerSizes(bid); - } - - let floor = (getBannerBidFloor(bid) == null || typeof getBannerBidFloor(bid) == 'undefined') ? 0.1 : getBannerBidFloor(bid); - const o = { - 'device': { - 'langauge': (global.navigator.language).split('-')[0], - 'dnt': (global.navigator.doNotTrack === 1 ? 1 : 0), - 'devicetype': isMobile() ? 4 : isConnectedTV() ? 3 : 2, - 'js': 1 - }, - 'at': 2, - 'site': {}, - 'tmax': 3000, - 'cur': ['USD'], - 'id': bid.bidId, - 'imp': [], - 'regs': { - 'ext': { - } - }, - 'user': { - 'ext': { - } - } - }; - - o.site['page'] = topLocation.href; - o.site['domain'] = topLocation.hostname; - o.site['search'] = topLocation.search; - o.site['domain'] = topLocation.hostname; - o.site['ref'] = topReferrer; - o.site['mobile'] = isMobile() ? 1 : 0; - const secure = topLocation.protocol.indexOf('https') === 0 ? 1 : 0; - - o.device['dnt'] = getDoNotTrack() ? 1 : 0; - - findAndFillParam(o.site, 'name', function() { - return global.top.document.title; - }); - - findAndFillParam(o.device, 'h', function() { - return global.screen.height; - }); - findAndFillParam(o.device, 'w', function() { - return global.screen.width; - }); - - let placement = getBannerBidParam(bid, 'placement'); - for (let j = 0; j < sizes.length; j++) { - let size = sizes[j]; - - o.imp.push({ - 'id': '' + j, - 'displaymanager': '' + BIDDER_CODE, - 'displaymanagerver': '' + ADAPTER_VERSION, - 'tagId': placement, - 'bidfloor': floor, - 'bidfloorcur': 'USD', - 'secure': secure, - 'banner': { - 'id': generateUUID(), - 'pos': 0, - 'w': size['w'], - 'h': size['h'] - } - }); - } - if (coppa) { - o.regs.ext = {'coppa': 1}; - } - if (bidderRequest && bidderRequest.gdprConsent) { - let { gdprApplies, consentString } = bidderRequest.gdprConsent; - o.regs.ext = {'gdpr': gdprApplies ? 1 : 0}; - o.user.ext = {'consent': consentString}; - } - - return o; -} +import { spec } from './advangelistsBidAdapter.js'; // eslint-disable-line prebid/validate-imports +import { registerBidder } from '../src/adapters/bidderFactory.js'; registerBidder(spec); diff --git a/modules/seedingAllianceBidAdapter.js b/modules/seedingAllianceBidAdapter.js index e287ea7ff78..e5fbebc8ffe 100755 --- a/modules/seedingAllianceBidAdapter.js +++ b/modules/seedingAllianceBidAdapter.js @@ -3,25 +3,44 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE} from '../src/mediaTypes.js'; -import {_map, deepSetValue, isArray, isEmpty, replaceAuctionPrice} from '../src/utils.js'; +import {generateUUID, deepSetValue, isEmpty, replaceAuctionPrice} from '../src/utils.js'; import {config} from '../src/config.js'; -import {convertOrtbRequestToProprietaryNative} from '../src/native.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const GVL_ID = 371; const BIDDER_CODE = 'seedingAlliance'; const DEFAULT_CUR = 'EUR'; const ENDPOINT_URL = 'https://b.nativendo.de/cds/rtb/bid?format=openrtb2.5&ssp=pb'; +const NATIVENDO_KEY = 'nativendo_id'; -const NATIVE_ASSET_IDS = { 0: 'title', 1: 'body', 2: 'sponsoredBy', 3: 'image', 4: 'cta', 5: 'icon' }; +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); -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' } -}; +const converter = ortbConverter({ + context: { + ttl: 360, + netRevenue: true + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + // set basic page, this might be updated later by adunit param + deepSetValue(request, 'site.page', bidderRequest.refererInfo.page); + deepSetValue(request, 'regs.ext.pb_ver', '$prebid.version$'); + deepSetValue(request, 'cur', [config.getConfig('currency') || DEFAULT_CUR]); + + // As this is client side, we get needed info from headers + delete request.device; + + return request; + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + // add tagid from params + imp.tagid = bidRequest.params.adUnitId; + + return imp; + } +}); export const spec = { code: BIDDER_CODE, @@ -33,109 +52,30 @@ export const spec = { }, buildRequests: (validBidRequests = [], bidderRequest) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let url = bidderRequest.refererInfo.page; - - const imps = validBidRequests.map((bidRequest, id) => { - const imp = { - id: String(id + 1), - tagid: bidRequest.params.adUnitId - }; - - /** - * Native Ad - */ - if (bidRequest.nativeParams) { - const assets = _map(bidRequest.nativeParams, (nativeAsset, key) => { - const props = NATIVE_PARAMS[key]; - - if (props) { - let wmin, hmin, w, h; - let aRatios = nativeAsset.aspect_ratios; - - if (aRatios && aRatios[0]) { - aRatios = aRatios[0]; - wmin = aRatios.min_width || 0; - hmin = aRatios.ratio_height * wmin / aRatios.ratio_width | 0; - } - - if (nativeAsset.sizes) { - const sizes = flatten(nativeAsset.sizes); - w = parseInt(sizes[0], 10); - h = parseInt(sizes[1], 10); - } - - const asset = { - id: props.id, - required: nativeAsset.required & 1 - }; - - asset[props.name] = { - len: nativeAsset.len, - type: props.type, - wmin, - hmin, - w, - h - }; - - return asset; - } else { - // TODO Filter impressions with required assets we don't support - } - }).filter(Boolean); - - imp.native = { - request: { - assets - } - }; - } else { - let sizes = transformSizes(bidRequest.sizes); - - imp.banner = { - format: sizes, - w: sizes[0] ? sizes[0].w : 0, - h: sizes[0] ? sizes[0].h : 0 - } - } + const oRtbRequest = converter.toORTB({bidRequests: validBidRequests, bidderRequest}); + let eids = getEids(validBidRequests[0]); + // check for url in params and set in site object + validBidRequests.forEach(bidRequest => { if (bidRequest.params.url) { - url = bidRequest.params.url; + deepSetValue(oRtbRequest, 'site.page', bidRequest.params.url); } - - return imp; }); - const request = { - id: bidderRequest.bidderRequestId, - site: { - page: url - }, - cur: [config.getConfig('currency.adServerCurrency') || DEFAULT_CUR], - imp: imps, - tmax: bidderRequest.timeout, - regs: { - ext: { - gdpr: 0, - pb_ver: '$prebid.version$' - } - } - }; - if (bidderRequest.gdprConsent) { - request.user = {}; + oRtbRequest.user = {}; - deepSetValue(request, 'user.ext.consent', bidderRequest.gdprConsent.consentString); - deepSetValue(request, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + deepSetValue(oRtbRequest, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(oRtbRequest, 'regs.ext.gdpr', (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean' && bidderRequest.gdprConsent.gdprApplies) ? 1 : 0); + deepSetValue(oRtbRequest, 'user.ext.eids', eids); } + let endpoint = config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL; + return { method: 'POST', - url: config.getConfig('seedingAlliance.endpoint') || ENDPOINT_URL, - data: JSON.stringify(request), + url: endpoint, + data: JSON.stringify(oRtbRequest), bidRequests: validBidRequests }; }, @@ -148,14 +88,13 @@ export const spec = { const { seatbid, cur } = serverResponse.body; const bidResponses = (typeof seatbid != 'undefined') ? flatten(seatbid.map(seat => seat.bid)).reduce((result, bid) => { - result[bid.impid - 1] = bid; + result[bid.impid] = bid; return result; }, []) : []; - return bidRequests - .map((bidRequest, id) => { - const bidResponse = bidResponses[id]; - + .map((bidRequest) => { + const bidId = bidRequest.bidId; + const bidResponse = bidResponses[bidId]; const type = bidRequest.nativeParams ? NATIVE : BANNER; if (bidResponse) { @@ -173,7 +112,7 @@ export const spec = { }; if (type === NATIVE) { - bidObject.native = parseNative(bidResponse); + bidObject.native = parseNative(bidResponse, bidRequest.nativeParams); bidObject.mediaType = NATIVE; } @@ -191,32 +130,62 @@ export const spec = { } }; -function transformSizes(requestSizes) { - if (!isArray(requestSizes)) { - return []; +const getNativendoID = () => { + let nativendoID = storage.localStorageIsEnabled() && + storage.getDataFromLocalStorage(NATIVENDO_KEY); + + if (!nativendoID) { + if (storage.localStorageIsEnabled()) { + nativendoID = generateUUID(); + storage.setDataInLocalStorage(NATIVENDO_KEY, nativendoID); + } + } + + return nativendoID; +} + +const getEids = (bidRequest) => { + const eids = []; + const nativendoID = getNativendoID(); + + if (nativendoID) { + const nativendoUserEid = { + source: 'nativendo.de', + uids: [ + { + id: nativendoID, + atype: 1 + } + ] + }; + + eids.push(nativendoUserEid); } - if (requestSizes.length === 2 && !isArray(requestSizes[0])) { - return [{ - w: parseInt(requestSizes[0], 10), - h: parseInt(requestSizes[1], 10) - }]; - } else if (isArray(requestSizes[0])) { - return requestSizes.map(item => ({ - w: parseInt(item[0], 10), - h: parseInt(item[1], 10) - })); + if (bidRequest.userIdAsEids) { + eids.push(bidRequest.userIdAsEids); } - return []; + return eids; } function flatten(arr) { return [].concat(...arr); } -function parseNative(bid) { - const { assets, link, imptrackers } = bid.adm.native; +function parseNative(bid, nativeParams) { + let native; + if (typeof bid.adm === 'string') { + try { + native = JSON.parse(bid.adm).native; + } catch (e) { + return; + } + } else { + native = bid.adm.native; + } + + const { assets, link, imptrackers } = native; let clickUrl = link.url.replace(/\$\{AUCTION_PRICE\}/g, bid.price); @@ -239,13 +208,34 @@ function parseNative(bid) { impressionTrackers: imptrackers || undefined }; - assets.forEach(asset => { - const kind = NATIVE_ASSET_IDS[asset.id]; - const content = kind && asset[NATIVE_PARAMS[kind].name]; + let nativeParamKeys = Object.keys(nativeParams); + let id = 0; + + nativeParamKeys.forEach(nativeParam => { + assets.forEach(asset => { + if (asset.id == id) { + switch (nativeParam) { + case 'title': + result.title = asset.title.text; + break; + case 'body': + case 'cta': + case 'sponsoredBy': + result[nativeParam] = asset.data.value; + break; + case 'image': + case 'icon': + result[nativeParam] = { + url: asset.img.url, + width: asset.img.w, + height: asset.img.h + }; + break; + } + } + }); - if (content) { - result[kind] = content.text || content.value || { url: content.url, width: content.w, height: content.h }; - } + id++; }); return result; diff --git a/modules/seedtagBidAdapter.js b/modules/seedtagBidAdapter.js index fb502ca60b5..98316d68800 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -10,6 +10,8 @@ import { _map, isArray, triggerPixel } from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync * @typedef {import('../src/adapters/bidderFactory.js').validBidRequests} validBidRequests + * @typedef {import('../src/adapters/bidderFactory.js').bidderRequest} bidderRequest + * @typedef {import('../src/adapters/bidderFactory.js').TimedOutBid} TimedOutBid */ /** @@ -25,12 +27,6 @@ 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_DISPLAY_PLACEMENTS = [ - 'inScreen', - 'inImage', - 'inArticle', - 'inBanner', -]; // Global Vendor List Id // https://iabeurope.eu/vendor-list-tcf-v2-0/ @@ -47,6 +43,22 @@ const deviceConnection = { UNKNOWN: 'unknown', }; +export const BIDFLOOR_CURRENCY = 'USD' + +function getBidFloor(bidRequest) { + let floorInfo = {}; + + if (typeof bidRequest.getFloor === 'function') { + floorInfo = bidRequest.getFloor({ + currency: BIDFLOOR_CURRENCY, + mediaType: '*', + size: '*' + }); + } + + return floorInfo?.floor; +} + const getConnectionType = () => { const connection = navigator.connection || @@ -86,8 +98,7 @@ function hasMandatoryDisplayParams(bid) { const p = bid.params; return ( !!p.publisherId && - !!p.adUnitId && - ALLOWED_DISPLAY_PLACEMENTS.indexOf(p.placement) > -1 + !!p.adUnitId ); } @@ -102,19 +113,7 @@ function hasMandatoryVideoParams(bid) { isArray(videoParams.playerSize) && videoParams.playerSize.length > 0; - switch (bid.params.placement) { - // instream accept only video format - case 'inStream': - return isValid && videoParams.context === 'instream'; - // outstream accept banner/native/video format - default: - return ( - isValid && - videoParams.context === 'outstream' && - hasBannerMediaType(bid) && - hasMandatoryDisplayParams(bid) - ); - } + return isValid } function buildBidRequest(validBidRequest) { @@ -128,6 +127,7 @@ function buildBidRequest(validBidRequest) { const bidRequest = { id: validBidRequest.bidId, transactionId: validBidRequest.ortb2Imp?.ext?.tid, + gpid: validBidRequest.ortb2Imp?.ext?.gpid, sizes: validBidRequest.sizes, supplyTypes: mediaTypes, adUnitId: params.adUnitId, @@ -141,6 +141,11 @@ function buildBidRequest(validBidRequest) { bidRequest.videoParams = getVideoParams(validBidRequest); } + const bidFloor = getBidFloor(validBidRequest) + if (bidFloor) { + bidRequest.bidFloor = bidFloor; + } + return bidRequest; } @@ -157,6 +162,10 @@ function getVideoParams(validBidRequest) { return videoParams; } +function isVideoOutstream(validBidRequest) { + return getVideoParams(validBidRequest).context === 'outstream'; +} + function buildBidResponse(seedtagBid) { const mediaType = mapMediaType(seedtagBid.mediaType); const bid = { @@ -271,15 +280,26 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid(bid) { - return hasVideoMediaType(bid) - ? hasMandatoryVideoParams(bid) - : hasMandatoryDisplayParams(bid); + const hasVideo = hasVideoMediaType(bid); + const hasBanner = hasBannerMediaType(bid); + + // when accept both mediatype but it must be outstream + if (hasVideo && hasBanner) { + return hasMandatoryVideoParams(bid) && isVideoOutstream(bid) && hasMandatoryDisplayParams(bid); + } else if (hasVideo) { + return hasMandatoryVideoParams(bid); + } else if (hasBanner) { + return hasMandatoryDisplayParams(bid); + } else { + return false; + } }, /** * Make a server request from the list of BidRequests. * - * @param {validBidRequests[]} - an array of bids + * @param {validBidRequests[]} validBidRequests an array of bids + * @param {bidderRequest} bidderRequest an array of bids * @return ServerRequest Info describing the request to the server. */ buildRequests(validBidRequests, bidderRequest) { @@ -293,7 +313,8 @@ export const spec = { auctionStart: bidderRequest.auctionStart || Date.now(), ttfb: ttfb(), bidRequests: _map(validBidRequests, buildBidRequest), - user: { topics: [], eids: [] } + user: { topics: [], eids: [] }, + site: {} }; if (payload.cmp) { @@ -341,6 +362,22 @@ export const spec = { payload.badv = bidderRequest.ortb2?.badv } + if (bidderRequest.ortb2?.device?.sua) { + payload.sua = bidderRequest.ortb2.device.sua + } + + if (bidderRequest.ortb2?.site?.cat) { + payload.site.cat = bidderRequest.ortb2.site.cat + } + + if (bidderRequest.ortb2?.site?.cattax) { + payload.site.cattax = bidderRequest.ortb2.site.cattax + } + + if (bidderRequest.ortb2?.site?.pagecat) { + payload.site.pagecat = bidderRequest.ortb2.site.pagecat + } + const payloadString = JSON.stringify(payload); return { @@ -386,7 +423,7 @@ export const spec = { /** * Register bidder specific code, which will execute if bidder timed out after an auction - * @param {data} Containing timeout specific data + * @param {TimedOutBid} data Containing timeout specific data */ onTimeout(data) { const url = getTimeoutUrl(data); @@ -395,7 +432,7 @@ export const spec = { /** * Function to call when the adapter wins the auction - * @param {bid} Bid information received from the server + * @param {Bid} bid The bid information received from the server */ onBidWon: function (bid) { if (bid && bid.nurl) { diff --git a/modules/setupadBidAdapter.js b/modules/setupadBidAdapter.js index 55677d51c56..4ee6dd7c085 100644 --- a/modules/setupadBidAdapter.js +++ b/modules/setupadBidAdapter.js @@ -1,26 +1,54 @@ import { _each, - createTrackPixelHtml, - deepAccess, isStr, getBidIdParameter, triggerPixel, logWarn, + deepSetValue, } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER } from '../src/mediaTypes.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'setupad'; const ENDPOINT = 'https://prebid.setupad.io/openrtb2/auction'; const SYNC_ENDPOINT = 'https://cookie.stpd.cloud/sync?'; -const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics'; +const REPORT_ENDPOINT = 'https://adapter-analytics.setupad.io/api/adapter-analytics?'; const GVLID = 1241; const TIME_TO_LIVE = 360; -const biddersCreativeIds = {}; - -function getEids(bidRequest) { - if (deepAccess(bidRequest, 'userIdAsEids')) return bidRequest.userIdAsEids; -} +export const biddersCreativeIds = {}; // export only for tests +const NET_REVENUE = true; +const TEST_REQUEST = 0; // used only for testing + +const converter = ortbConverter({ + context: { + netRevenue: NET_REVENUE, + ttl: TIME_TO_LIVE, + }, + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + deepSetValue( + imp, + 'ext.prebid.storedrequest.id', + getBidIdParameter('placement_id', bidRequest.params) + ); + return imp; + }, + request(buildRequest, imps, bidderRequest, context) { + const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'test', TEST_REQUEST); + deepSetValue( + request, + 'ext.prebid.storedrequest.id', + getBidIdParameter( + 'account_id', + bidderRequest.bids.find((bid) => bid.hasOwnProperty('params')).params + ) + ); + deepSetValue(request, 'setupad', 'adapter'); + return request; + }, +}); export const spec = { code: BIDDER_CODE, @@ -28,101 +56,26 @@ export const spec = { gvlid: GVLID, isBidRequestValid: function (bid) { - return !!(bid.params.placement_id && isStr(bid.params.placement_id)); + return !!( + bid.params.placement_id && + isStr(bid.params.placement_id) && + bid.params.account_id && + isStr(bid.params.account_id) + ); }, buildRequests: function (validBidRequests, bidderRequest) { - const requests = []; - - _each(validBidRequests, function (bid) { - const id = getBidIdParameter('placement_id', bid.params); - const accountId = getBidIdParameter('account_id', bid.params); - const auctionId = bid.auctionId; - const bidId = bid.bidId; - const eids = getEids(bid) || undefined; - let sizes = bid.sizes; - if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; - - const site = { - page: bidderRequest?.refererInfo?.page, - ref: bidderRequest?.refererInfo?.ref, - domain: bidderRequest?.refererInfo?.domain, - }; - const device = { - w: bidderRequest?.ortb2?.device?.w, - h: bidderRequest?.ortb2?.device?.h, - }; - - const payload = { - id: bid?.bidderRequestId, - ext: { - prebid: { - storedrequest: { - id: accountId || 'default', - }, - }, - }, - user: { ext: { eids } }, - device, - site, - imp: [], - }; - - const imp = { - id: bid.adUnitCode, - ext: { - prebid: { - storedrequest: { id }, - }, - }, - }; - - if (deepAccess(bid, 'mediaTypes.banner')) { - imp.banner = { - format: (sizes || []).map((s) => { - return { w: s[0], h: s[1] }; - }), - }; - } - - payload.imp.push(imp); - - const gdprConsent = bidderRequest && bidderRequest.gdprConsent; - const uspConsent = bidderRequest && bidderRequest.uspConsent; - - if (gdprConsent || uspConsent) { - payload.regs = { ext: {} }; - - if (uspConsent) payload.regs.ext.us_privacy = uspConsent; - - if (gdprConsent) { - if (typeof gdprConsent.gdprApplies !== 'undefined') { - payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; - } - - if (typeof gdprConsent.consentString !== 'undefined') { - payload.user.ext.consent = gdprConsent.consentString; - } - } - } - const params = bid.params; - - requests.push({ - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(payload), - options: { - contentType: 'text/plain', - withCredentials: true, - }, - - bidId, - params, - auctionId, - }); - }); - - return requests; + const data = converter.toORTB({ validBidRequests, bidderRequest }); + + return { + method: 'POST', + url: ENDPOINT, + data, + options: { + contentType: 'text/plain', + withCredentials: true, + }, + }; }, interpretResponse: function (serverResponse, bidRequest) { @@ -136,40 +89,22 @@ export const spec = { return []; } - const serverBody = serverResponse.body; - const bidResponses = []; - - _each(serverBody.seatbid, (res) => { + // set a seat for creativeId for triggerPixel url + _each(serverResponse.body.seatbid, (res) => { _each(res.bid, (bid) => { - const requestId = bidRequest.bidId; - const params = bidRequest.params; - const { ad, adUrl } = getAd(bid); - - const bidResponse = { - requestId, - params, - cpm: bid.price, - width: bid.w, - height: bid.h, - creativeId: bid.id, - currency: serverBody.cur, - netRevenue: true, - ttl: TIME_TO_LIVE, - meta: { - advertiserDomains: bid.adomain || [], - }, - }; - - // set a seat for creativeId for triggerPixel url - biddersCreativeIds[bidResponse.creativeId] = res.seat; - - bidResponse.ad = ad; - bidResponse.adUrl = adUrl; - bidResponses.push(bidResponse); + biddersCreativeIds[bid.crid] = res.seat; }); }); - return bidResponses; + // used for a test case "should update biddersCreativeIds correctly" to return early and not throw ORTB error + if (serverResponse.testCase === 1) return; + + const bids = converter.fromORTB({ + response: serverResponse.body, + request: bidRequest.data, + }).bids; + + return bids; }, getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { @@ -225,17 +160,23 @@ export const spec = { if (!placementIds) return; - let extraBidParams = ''; - // find the winning bidder by using creativeId as identification if (biddersCreativeIds.hasOwnProperty(bid.creativeId) && biddersCreativeIds[bid.creativeId]) { bidder = biddersCreativeIds[bid.creativeId]; } - // Add extra parameters - extraBidParams = `&cpm=${bid.originalCpm}¤cy=${bid.originalCurrency}`; + const queryParams = []; + queryParams.push(`event=bidWon`); + queryParams.push('bidder=' + bidder); + queryParams.push('placementIds=' + placementIds); + queryParams.push('auctionId=' + auctionId); + queryParams.push('cpm=' + bid.originalCpm); + queryParams.push('currency=' + bid.originalCurrency); + queryParams.push('timestamp=' + Date.now()); - const url = `${REPORT_ENDPOINT}?event=bidWon&bidder=${bidder}&placementIds=${placementIds}&auctionId=${auctionId}${extraBidParams}×tamp=${Date.now()}`; + const strQueryParams = queryParams.join('&'); + + const url = REPORT_ENDPOINT + strQueryParams; triggerPixel(url); }, }; @@ -250,22 +191,4 @@ function getBidders(serverResponse) { } } -function getAd(bid) { - let ad, adUrl; - - switch (deepAccess(bid, 'ext.prebid.type')) { - default: - if (bid.adm && bid.nurl) { - ad = bid.adm; - ad += createTrackPixelHtml(decodeURIComponent(bid.nurl)); - } else if (bid.adm) { - ad = bid.adm; - } else if (bid.nurl) { - adUrl = bid.nurl; - } - } - - return { ad, adUrl }; -} - registerBidder(spec); diff --git a/modules/setupadBidAdapter.md b/modules/setupadBidAdapter.md index 0d4f0ef392e..c11e8eb4bae 100644 --- a/modules/setupadBidAdapter.md +++ b/modules/setupadBidAdapter.md @@ -26,7 +26,7 @@ const adUnits = [ bidder: 'setupad', params: { placement_id: '123', //required - account_id: '123', //optional + account_id: '123', //required }, }, ], diff --git a/modules/sharethroughAnalyticsAdapter.md b/modules/sharethroughAnalyticsAdapter.md new file mode 100644 index 00000000000..4acf8eacfd0 --- /dev/null +++ b/modules/sharethroughAnalyticsAdapter.md @@ -0,0 +1,41 @@ +# Overview + +```txt +Module Name: Sharethrough Analytics Adapter +Module Type: Analytics Adapter +Maintainer: pubgrowth.engineering@sharethrough.com +``` + +#### About + +This analytics adapter collects data about win/loss events (beacon firings) from each auction run on your site. This data is communicated to Sharethrough via API calls the analytics adapter makes to an endpoint dedicated to the collection of beacon information. Sharethrough uses this information to improve its services as a SSP. + +This analytics adapter is free to use. + +#### Configuration + +In order to guarantee consistent reporting events, we recommend +including the GPT Pre-Auction Module, `gptPreAuction`. This module is included +by default when Prebid is downloaded. + +If you are compiling from source, this might look something like: + +```sh +gulp bundle --modules=gptPreAuction,sharethroughBidAdapter,sharethroughAnalyticsAdapter +``` + +Please note that the above snippet is a "bare bones" example - you will likely want to include other modules as well. A more realistic example might look something like the example below (with other bid adapters also included in the list as needed): + +```sh +gulp bundle --modules=gptPreAuction,consentManagement,consentManagementGpp,consentManagementUsp,enrichmentFpdModule,gdprEnforcement,sharethroughBidAdapter,sharethroughAnalyticsAdapter +``` + +Enable the Sharethrough Analytics Adapter in Prebid.js using the analytics provider `sharethrough` as seen in the example below. + +#### Example Configuration + +```js +pbjs.enableAnalytics({ + provider: 'sharethrough', +}); +``` diff --git a/modules/sharethroughBidAdapter.js b/modules/sharethroughBidAdapter.js index 590fddca079..7144370dc9c 100644 --- a/modules/sharethroughBidAdapter.js +++ b/modules/sharethroughBidAdapter.js @@ -1,13 +1,14 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { deepAccess, generateUUID, inIframe, mergeDeep } from '../src/utils.js'; +import { deepAccess, generateUUID, inIframe, isPlainObject, logWarn, mergeDeep } from '../src/utils.js'; const VERSION = '4.3.0'; const BIDDER_CODE = 'sharethrough'; const SUPPLY_ID = 'WYu2BXv1'; const STR_ENDPOINT = `https://btlr.sharethrough.com/universal/v1?supply_id=${SUPPLY_ID}`; +const IDENTIFIER_PREFIX = 'Sharethrough:'; // this allows stubbing of utility function that is used internally by the sharethrough adapter export const sharethroughInternal = { @@ -18,7 +19,7 @@ export const sharethroughAdapterSpec = { code: BIDDER_CODE, supportedMediaTypes: [VIDEO, BANNER], gvlid: 80, - isBidRequestValid: (bid) => !!bid.params.pkey && bid.bidder === BIDDER_CODE, + isBidRequestValid: (bid) => !!bid.params.pkey, buildRequests: (bidRequests, bidderRequest) => { const timeout = bidderRequest.timeout; @@ -68,6 +69,11 @@ export const sharethroughAdapterSpec = { req.device.ext['cdep'] = bidderRequest.ortb2.device.ext.cdep; } + // if present, merge device object from ortb2 into `req.device` + if (bidderRequest?.ortb2?.device) { + mergeDeep(req.device, bidderRequest.ortb2.device); + } + req.user = nullish(firstPartyData.user, {}); if (!req.user.ext) req.user.ext = {}; req.user.ext.eids = bidRequests[0].userIdAsEids || []; @@ -108,7 +114,7 @@ export const sharethroughAdapterSpec = { const videoRequest = deepAccess(bidReq, 'mediaTypes.video'); - if (bidderRequest.fledgeEnabled && bidReq.mediaTypes.banner) { + if (bidderRequest.paapi?.enabled && bidReq.mediaTypes.banner) { mergeDeep(impression, { ext: { ae: 1 } }); // ae = auction environment; if this is 1, ad server knows we have a fledge auction } @@ -124,43 +130,48 @@ export const sharethroughAdapterSpec = { [w, h] = videoRequest.playerSize[0]; } - const getVideoPlacementValue = (vidReq) => { - if (vidReq.plcmt) { - return vidReq.placement; - } else { - return vidReq.context === 'instream' ? 1 : +deepAccess(vidReq, 'placement', 4); + /** + * Applies a specified property to an impression object if it is present in the video request + * @param {string} prop A property to apply to the impression object + * @param {object} vidReq A video request object from which to extract the property + * @param {object} imp A video impression object to which to apply the property + */ + const applyVideoProperty = (prop, vidReq, imp) => { + const propIsTypeArray = ['api', 'battr', 'mimes', 'playbackmethod', 'protocols'].includes(prop); + if (propIsTypeArray) { + const notAssignable = (!Array.isArray(vidReq[prop]) || vidReq[prop].length === 0) && vidReq[prop]; + if (notAssignable) { + logWarn(`${IDENTIFIER_PREFIX} Invalid video request property: "${prop}" must be an array with at least 1 entry. Value supplied: "${vidReq[prop]}". This will not be added to the bid request.`); + return; + } + } + if (vidReq[prop]) { + imp.video[prop] = vidReq[prop]; } }; impression.video = { pos: nullish(videoRequest.pos, 0), topframe: inIframe() ? 0 : 1, - skip: nullish(videoRequest.skip, 0), - linearity: nullish(videoRequest.linearity, 1), - minduration: nullish(videoRequest.minduration, 5), - maxduration: nullish(videoRequest.maxduration, 60), - playbackmethod: videoRequest.playbackmethod || [2], - api: getVideoApi(videoRequest), - mimes: videoRequest.mimes || ['video/mp4'], - protocols: getVideoProtocols(videoRequest), w, h, - startdelay: nullish(videoRequest.startdelay, 0), - skipmin: nullish(videoRequest.skipmin, 0), - skipafter: nullish(videoRequest.skipafter, 0), - placement: getVideoPlacementValue(videoRequest), - plcmt: videoRequest.plcmt ? videoRequest.plcmt : null, }; - if (videoRequest.delivery) impression.video.delivery = videoRequest.delivery; - if (videoRequest.companiontype) impression.video.companiontype = videoRequest.companiontype; - if (videoRequest.companionad) impression.video.companionad = videoRequest.companionad; + const propertiesToConsider = [ + 'api', 'battr', 'companionad', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' + ] + + propertiesToConsider.forEach(propertyToConsider => { + applyVideoProperty(propertyToConsider, videoRequest, impression); + }); } else { impression.banner = { pos: deepAccess(bidReq, 'mediaTypes.banner.pos', 0), topframe: inIframe() ? 0 : 1, format: bidReq.sizes.map((size) => ({ w: +size[0], h: +size[1] })), }; + const battr = deepAccess(bidReq, 'mediaTypes.banner.battr', null) || deepAccess(bidReq, 'ortb2Imp.banner.battr') + if (battr) impression.banner.battr = battr } return { @@ -242,7 +253,7 @@ export const sharethroughAdapterSpec = { if (fledgeAuctionEnabled) { return { bids: bidsFromExchange, - fledgeAuctionConfigs: body.ext?.auctionConfigs || {}, + paapi: body.ext?.auctionConfigs || {}, }; } else { return bidsFromExchange; @@ -266,24 +277,6 @@ export const sharethroughAdapterSpec = { onSetTargeting: (bid) => {}, }; -function getVideoApi({ api }) { - let defaultValue = [2]; - if (api && Array.isArray(api) && api.length > 0) { - return api; - } else { - return defaultValue; - } -} - -function getVideoProtocols({ protocols }) { - let defaultValue = [2, 3, 5, 6, 7, 8]; - if (protocols && Array.isArray(protocols) && protocols.length > 0) { - return protocols; - } else { - return defaultValue; - } -} - function getBidRequestFloor(bid) { let floor = null; if (typeof bid.getFloor === 'function') { @@ -292,7 +285,7 @@ function getBidRequestFloor(bid) { 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))) { + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = parseFloat(floorInfo.floor); } } diff --git a/modules/shinezBidAdapter.js b/modules/shinezBidAdapter.js index 47fca317de2..89f39284bed 100644 --- a/modules/shinezBidAdapter.js +++ b/modules/shinezBidAdapter.js @@ -2,33 +2,28 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; const BIDDER_CODE = 'shinez'; const ADAPTER_VERSION = '1.0.0'; const TTL = 360; const CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.sweetgum.io/'; +const BASE_URL = 'https://hb.sweetgum.io/'; const MODES = { PRODUCTION: 'hb-sz-multi', TEST: 'hb-multi-sz-test' -} -const SUPPORTED_SYNC_METHODS = { - IFRAME: 'iframe', - PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -59,7 +54,7 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, BASE_URL, MODES), data: combinedRequestsObject } }, @@ -68,32 +63,7 @@ export const spec = { if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -103,20 +73,20 @@ export const spec = { getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; for (const response of serverResponses) { - if (syncOptions.iframeEnabled && response.body.params.userSyncURL) { + if (syncOptions.iframeEnabled && deepAccess(response, 'body.params.userSyncURL')) { syncs.push({ type: 'iframe', - url: response.body.params.userSyncURL + url: deepAccess(response, 'body.params.userSyncURL') }); } - if (syncOptions.pixelEnabled && isArray(response.body.params.userSyncPixels)) { + if (syncOptions.pixelEnabled && isArray(deepAccess(response, 'body.params.userSyncPixels'))) { const pixels = response.body.params.userSyncPixels.map(pixel => { return { type: 'image', url: pixel } - }) - syncs.push(...pixels) + }); + syncs.push(...pixels); } } return syncs; @@ -134,314 +104,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the the ad sizes array from the bid - * @param bid {bid} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - let scStr = `${schainObject.ver},${schainObject.complete}`; - schainObject.nodes.forEach((node) => { - scStr += '!'; - scStr += `${getEncodedValIfNotEmpty(node.asi)},`; - scStr += `${getEncodedValIfNotEmpty(node.sid)},`; - scStr += `${node.hp ? encodeURIComponent(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 !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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && 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'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack == 'yes' || navigator.doNotTrack == '1' || navigator.msDoNotTrack == '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - session_id: getBidIdParameter('auctionId', generalObject), - tmax: timeout - }; - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest.ortb2 && bidderRequest.ortb2.site) { - generalParams.referrer = bidderRequest.ortb2.site.ref; - generalParams.page_url = bidderRequest.ortb2.site.page; - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = generalParams.referrer || deepAccess(bidderRequest, 'refererInfo.referer'); - generalParams.page_url = generalParams.page_url || config.getConfig('pageUrl') || deepAccess(window, 'location.href'); - } - - return generalParams; -} diff --git a/modules/shinezRtbBidAdapter.js b/modules/shinezRtbBidAdapter.js index d1d9f36a569..f2034bd1992 100644 --- a/modules/shinezRtbBidAdapter.js +++ b/modules/shinezRtbBidAdapter.js @@ -1,327 +1,29 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} 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 {config} from '../src/config.js'; +import { + isBidRequestValid, + createBuildRequestsFn, + createInterpretResponseFn, createUserSyncGetter +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'shinezRtb'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.sweetgum.io`; } -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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '') { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - const params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.sweetgum.io/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.sweetgum.io/api/sync/image/${params}` - }); - } - return syncs; -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.sweetgum.io/api/sync/iframe', + imageSyncUrl: 'https://sync.sweetgum.io/api/sync/image', +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/showheroes-bsBidAdapter.js b/modules/showheroes-bsBidAdapter.js index bd2706a21d5..afac0a88567 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -1,193 +1,114 @@ import { deepAccess, - getWindowTop, + deepSetValue, triggerPixel, - logInfo, - logError, getBidIdParameter + isFn, + logInfo } from '../src/utils.js'; -import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; +import { ortbConverter } from '../libraries/ortbConverter/converter.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO, BANNER } from '../src/mediaTypes.js'; -import { loadExternalScript } from '../src/adloader.js'; +import { VIDEO } from '../src/mediaTypes.js'; -const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; -const STAGE_ENDPOINT = 'https://bid-service.stage.showheroes.com/api/v1/bid'; -const VIRALIZE_ENDPOINT = 'https://ads.viralize.tv/prebid-sh/'; -const PROD_PUBLISHER_TAG = 'https://static.showheroes.com/publishertag.js'; -const STAGE_PUBLISHER_TAG = 'https://pubtag.stage.showheroes.com/publishertag.js'; -const PROD_VL = 'https://video-library.showheroes.com'; -const STAGE_VL = 'https://video-library.stage.showheroes.com'; +const ENDPOINT = 'https://ads.viralize.tv/openrtb2/auction/'; const BIDDER_CODE = 'showheroes-bs'; const TTL = 300; -function getEnvURLs(isStage) { - return { - pubTag: isStage ? STAGE_PUBLISHER_TAG : PROD_PUBLISHER_TAG, - vlHost: isStage ? STAGE_VL : PROD_VL - } -} - -const GVLID = 111; - -export const spec = { - code: BIDDER_CODE, - gvlid: GVLID, - aliases: ['showheroesBs'], - supportedMediaTypes: [VIDEO, BANNER], - isBidRequestValid: function(bid) { - return !!bid.params.playerId || !!bid.params.unitId; +const converter = ortbConverter({ + context: { + netRevenue: true, + ttl: TTL, + currency: 'EUR', + mediaType: VIDEO, }, - buildRequests: function(validBidRequests, bidderRequest) { - let adUnits = []; - const pageURL = validBidRequests[0].params.contentPageUrl || - bidderRequest.refererInfo.canonicalUrl || - deepAccess(window, 'location.href'); - const isStage = !!validBidRequests[0].params.stage; - const isViralize = !!validBidRequests[0].params.unitId; - const isOutstream = deepAccess(validBidRequests[0], 'mediaTypes.video.context') === 'outstream'; - const isCustomRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.customRender'); - const isNodeRender = deepAccess(validBidRequests[0], 'params.outstreamOptions.slot') || deepAccess(validBidRequests[0], 'params.outstreamOptions.iframe'); - const isNativeRender = deepAccess(validBidRequests[0], 'renderer'); - const outstreamOptions = deepAccess(validBidRequests[0], 'params.outstreamOptions'); - const isBanner = !!validBidRequests[0].mediaTypes.banner || (isOutstream && !(isCustomRender || isNativeRender || isNodeRender)); - const defaultSchain = validBidRequests[0].schain || {}; - - const consentData = bidderRequest.gdprConsent || {}; - const uspConsent = bidderRequest.uspConsent || ''; - const gdprConsent = { - apiVersion: consentData.apiVersion || 2, - gdprApplies: consentData.gdprApplies || 0, - consentString: consentData.consentString || '', + imp(buildImp, bidRequest, context) { + const imp = buildImp(bidRequest, context); + const videoContext = deepAccess(bidRequest, 'mediaTypes.video.context'); + deepSetValue(imp, 'video.ext.context', videoContext); + imp.ext = imp.ext || {}; + imp.ext.params = bidRequest.params; + imp.ext.adUnitCode = bidRequest.adUnitCode; + + if (!imp.displaymanager) { + imp.displaymanager = 'Prebid.js'; + imp.displaymanagerver = '$prebid.version$'; // prebid version } - validBidRequests.forEach((bid) => { - const videoSizes = getVideoSizes(bid); - const bannerSizes = getBannerSizes(bid); - const vpaidMode = getBidIdParameter('vpaidMode', bid.params); - - const makeBids = (type, size, isViralize) => { - let context = ''; - let streamType = 2; - - if (type === BANNER) { - streamType = 5; - } else { - context = deepAccess(bid, 'mediaTypes.video.context'); - if (vpaidMode && context === 'instream') { - streamType = 1; - } - if (context === 'outstream') { - streamType = 5; - } - } + if (!isFn(bidRequest.getFloor)) { + return imp + } - let rBid = { - type: streamType, - adUnitCode: bid.adUnitCode, - bidId: bid.bidId, - context: context, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - auctionId: bidderRequest.auctionId, - start: +new Date(), - timeout: 3000, - params: bid.params, - schain: bid.schain || defaultSchain - }; + let floor = bidRequest.getFloor({ + currency: 'EUR', + mediaType: '*', + size: '*', + }); + if (!isNaN(floor?.floor) && floor?.currency === 'EUR') { + imp.bidfloor = floor.floor; + imp.bidfloorcur = 'EUR'; + } + return imp; + }, - if (isViralize) { - rBid.unitId = getBidIdParameter('unitId', bid.params); - rBid.sizes = size; - rBid.mediaTypes = { - [type]: {'context': context} - }; - } else { - rBid.playerId = getBidIdParameter('playerId', bid.params); - rBid.mediaType = type; - rBid.size = { - width: size[0], - height: size[1] - }; - rBid.gdprConsent = gdprConsent; - rBid.uspConsent = uspConsent; - } + bidResponse(buildBidResponse, bid, context) { + const bidResponse = buildBidResponse(bid, context); - return rBid; + if (context.imp?.video?.ext?.context === 'outstream') { + const renderConfig = { + rendererUrl: bid.ext?.rendererConfig?.rendererUrl, + renderFunc: bid.ext?.rendererConfig?.renderFunc, + renderOptions: bid.ext?.rendererConfig?.renderOptions, }; - - if (isViralize) { - if (videoSizes && videoSizes[0]) { - adUnits.push(makeBids(VIDEO, videoSizes, isViralize)); - } - if (bannerSizes && bannerSizes[0]) { - adUnits.push(makeBids(BANNER, bannerSizes, isViralize)); - } - } else { - videoSizes.forEach((size) => { - adUnits.push(makeBids(VIDEO, size)); - }); - - bannerSizes.forEach((size) => { - adUnits.push(makeBids(BANNER, size)); - }); + if (renderConfig.renderFunc && renderConfig.rendererUrl) { + bidResponse.renderer = createRenderer(bidResponse, renderConfig); } - }); - - let endpointUrl; - let data; + } + bidResponse.callbacks = bid.ext?.callbacks; + bidResponse.extra = bid.ext?.extra; + return bidResponse; + }, +}) - const QA = validBidRequests[0].params.qa || {}; +const GVLID = 111; - if (isViralize) { - endpointUrl = VIRALIZE_ENDPOINT; - data = { - 'bidRequests': adUnits, - 'context': { - 'gdprConsent': gdprConsent, - 'uspConsent': uspConsent, - 'schain': defaultSchain, - 'pageURL': QA.pageURL || encodeURIComponent(pageURL) - } - } - } else { - endpointUrl = isStage ? STAGE_ENDPOINT : PROD_ENDPOINT; +export const spec = { + code: BIDDER_CODE, + gvlid: GVLID, + aliases: ['showheroesBs'], + supportedMediaTypes: [VIDEO], + isBidRequestValid: (bid) => { + return !!bid.params.unitId; + }, + buildRequests: (bidRequests, bidderRequest) => { + const QA = bidRequests[0].params.qa; - data = { - 'user': [], - 'meta': { - 'adapterVersion': 2, - 'pageURL': QA.pageURL || encodeURIComponent(pageURL), - 'vastCacheEnabled': (!!config.getConfig('cache') && !isBanner && !outstreamOptions) || false, - 'isDesktop': getWindowTop().document.documentElement.clientWidth > 700, - 'xmlAndTag': !!(isOutstream && isCustomRender) || false, - 'stage': isStage || undefined - }, - 'requests': adUnits, - 'debug': validBidRequests[0].params.debug || false, - } - } + const ortbData = converter.toORTB({ bidRequests, bidderRequest }) return { - url: QA.endpoint || endpointUrl, + url: QA?.endpoint || ENDPOINT, method: 'POST', - options: {contentType: 'application/json', accept: 'application/json'}, - data: data + data: ortbData, }; }, - interpretResponse: function(response, request) { - return createBids(response.body, request.data); + interpretResponse: (response, request) => { + if (!response.body) { + return []; + } + + const bids = converter.fromORTB({response: response.body, request: request.data}).bids; + return bids; }, - getUserSyncs: function(syncOptions, serverResponses) { + getUserSyncs: (syncOptions, serverResponses) => { const syncs = []; - if (!serverResponses.length || !serverResponses[0].body.userSync) { + if (!serverResponses.length || !serverResponses[0].body?.ext?.userSync) { return syncs; } - const userSync = serverResponses[0].body.userSync; + const userSync = serverResponses[0].body.ext.userSync; - if (syncOptions.iframeEnabled) { - (userSync.iframes || []).forEach(url => { + if (syncOptions.iframeEnabled && userSync?.iframes?.length) { + userSync.iframes.forEach(url => { syncs.push({ type: 'iframe', url @@ -203,6 +124,7 @@ export const spec = { }); }); } + return syncs; }, @@ -216,177 +138,33 @@ export const spec = { }, }; -function createBids(bidRes, reqData) { - if (!bidRes) { - return []; - } - const responseBids = bidRes.bids || bidRes.bidResponses; - if (!Array.isArray(responseBids) || responseBids.length < 1) { - return []; - } - - const bids = []; - const bidMap = {}; - (reqData.requests || reqData.bidRequests || []).forEach((bid) => { - bidMap[bid.bidId] = bid; - }); - - responseBids.forEach(function (bid) { - const requestId = bid.bidId || bid.requestId; - const reqBid = bidMap[requestId]; - const currentBidParams = reqBid.params; - const isViralize = !!reqBid.params.unitId; - const size = { - width: bid.width || bid.size.width, - height: bid.height || bid.size.height - }; - - let bidUnit = {}; - bidUnit.cpm = bid.cpm; - bidUnit.requestId = requestId; - bidUnit.adUnitCode = reqBid.adUnitCode; - bidUnit.currency = bid.currency; - bidUnit.mediaType = bid.mediaType || VIDEO; - bidUnit.ttl = TTL; - bidUnit.creativeId = 'c_' + requestId; - bidUnit.netRevenue = true; - bidUnit.width = size.width; - bidUnit.height = size.height; - bidUnit.meta = { - advertiserDomains: bid.adomain || [] - }; - if (bid.vastXml) { - bidUnit.vastXml = bid.vastXml; - bidUnit.adResponse = { - content: bid.vastXml, - }; - } - if (bid.vastTag || bid.vastUrl) { - bidUnit.vastUrl = bid.vastTag || bid.vastUrl; +function outstreamRender(response, renderConfig) { + response.renderer.push(() => { + const func = deepAccess(window, renderConfig.renderFunc); + if (!isFn(func)) { + return; } - if (bid.mediaType === BANNER) { - bidUnit.ad = getBannerHtml(bid, reqBid, reqData); - } else if (bid.context === 'outstream') { - const renderer = Renderer.install({ - id: requestId, - url: 'https://static.showheroes.com/renderer.js', - adUnitCode: reqBid.adUnitCode, - config: { - playerId: reqBid.playerId, - width: size.width, - height: size.height, - vastUrl: bid.vastTag, - vastXml: bid.vastXml, - ad: bid.ad, - debug: reqData.debug, - isStage: reqData.meta && !!reqData.meta.stage, - isViralize: isViralize, - customRender: getBidIdParameter('customRender', currentBidParams.outstreamOptions), - slot: getBidIdParameter('slot', currentBidParams.outstreamOptions), - iframe: getBidIdParameter('iframe', currentBidParams.outstreamOptions), - } - }); - renderer.setRender(outstreamRender); - bidUnit.renderer = renderer; + const renderPayload = { ...renderConfig.renderOptions }; + if (response.vastXml) { + renderPayload.adResponse = { + content: response.vastXml, + }; } - bids.push(bidUnit); + func(renderPayload); }); - - return bids; } -function outstreamRender(bid) { - let embedCode; - if (bid.renderer.config.isViralize) { - embedCode = createOutstreamEmbedCodeV2(bid); - } else { - embedCode = createOutstreamEmbedCode(bid); - } - if (typeof bid.renderer.config.customRender === 'function') { - bid.renderer.config.customRender(bid, embedCode); - } else { - try { - const inIframe = getBidIdParameter('iframe', bid.renderer.config); - if (inIframe && window.document.getElementById(inIframe).nodeName === 'IFRAME') { - const iframe = window.document.getElementById(inIframe); - let framedoc = iframe.contentDocument || (iframe.contentWindow && iframe.contentWindow.document); - framedoc.body.appendChild(embedCode); - return; - } - - const slot = getBidIdParameter('slot', bid.renderer.config) || bid.adUnitCode; - if (slot && window.document.getElementById(slot)) { - window.document.getElementById(slot).appendChild(embedCode); - } else if (slot) { - logError('[ShowHeroes][renderer] Error: spot not found'); - } - } catch (err) { - logError('[ShowHeroes][renderer] Error:' + err.message); - } - } -} - -function createOutstreamEmbedCode(bid) { - const isStage = getBidIdParameter('isStage', bid.renderer.config); - const urls = getEnvURLs(isStage); - - const fragment = window.document.createDocumentFragment(); - - let script = loadExternalScript(urls.pubTag, 'outstream', function () { - window.ShowheroesTag = this; +function createRenderer(bid, renderConfig) { + const renderer = Renderer.install({ + id: bid.id, + url: renderConfig.rendererUrl, + loaded: false, + adUnitCode: bid.adUnitCode, }); - script.setAttribute('data-player-host', urls.vlHost); - - const spot = window.document.createElement('div'); - spot.setAttribute('class', 'showheroes-spot'); - spot.setAttribute('data-player', getBidIdParameter('playerId', bid.renderer.config)); - spot.setAttribute('data-debug', getBidIdParameter('debug', bid.renderer.config)); - spot.setAttribute('data-ad-vast-tag', getBidIdParameter('vastUrl', bid.renderer.config)); - spot.setAttribute('data-stream-type', 'outstream'); - - fragment.appendChild(spot); - fragment.appendChild(script); - return fragment; -} - -function createOutstreamEmbedCodeV2(bid) { - const range = document.createRange(); - range.selectNode(document.getElementsByTagName('body')[0]); - return range.createContextualFragment(getBidIdParameter('ad', bid.renderer.config)); -} - -function getBannerHtml (bid, reqBid, reqData) { - const isStage = !!reqData.meta.stage; - const urls = getEnvURLs(isStage); - return ` - - - -
- - `; -} - -function getVideoSizes(bidRequest) { - return formatSizes(deepAccess(bidRequest, 'mediaTypes.video.playerSize') || []); -} - -function getBannerSizes(bidRequest) { - return formatSizes(deepAccess(bidRequest, 'mediaTypes.banner.sizes') || []); -} - -function formatSizes(sizes) { - if (!sizes || !sizes.length) { - return [] - } - return Array.isArray(sizes[0]) ? sizes : [sizes]; + renderer.setRender((render) => { + return outstreamRender(render, renderConfig); + }); + return renderer; } registerBidder(spec); diff --git a/modules/showheroes-bsBidAdapter.md b/modules/showheroes-bsBidAdapter.md index a32a77a2525..f306593a783 100644 --- a/modules/showheroes-bsBidAdapter.md +++ b/modules/showheroes-bsBidAdapter.md @@ -1,16 +1,14 @@ # Overview +``` Module Name: ShowHeroes Bidder Adapter - Module Type: Bidder Adapter - Alias: showheroesBs - Maintainer: tech@showheroes.com - +``` # Description -Module that connects to ShowHeroes demand source to fetch bids. +A module that connects to ShowHeroes demand source to fetch bids. # Test Parameters ``` @@ -27,122 +25,7 @@ Module that connects to ShowHeroes demand source to fetch bids. { bidder: "showheroes-bs", params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - vpaidMode: true // by default is 'false' - } - } - ] - }, - { - // if you have adSlot renderer or oustream should be returned as banner - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - } - } - ] - }, - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - - outstreamOptions: { - // Required for the outstream renderer to exact node, one of - iframe: 'iframe_id', - // or - slot: 'slot_id' - } - } - } - ] - }, - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'outstream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - - outstreamOptions: { - // Custom outstream rendering function - customRender: function(bid, embedCode) { - // Example with embedCode - someContainer.appendChild(embedCode); - - // bid config data - var vastUrl = bid.renderer.config.vastUrl; - var vastXML = bid.renderer.config.vastXML; - var videoWidth = bid.renderer.config.width; - var videoHeight = bid.renderer.config.height; - var playerId = bid.renderer.config.playerId; - }, - } - } - } - ] - }, - { - code: 'banner', - mediaTypes: { - banner: { - sizes: [[640, 480]], - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - playerId: '0151f985-fb1a-4f37-bb26-cfc62e43ec05', - } - } - ] - } - ]; -``` - -# Test Parameters (V2) -``` - var adUnits = [ - { - code: 'video', - mediaTypes: { - video: { - playerSize: [640, 480], - context: 'instream', - } - }, - bids: [ - { - bidder: "showheroes-bs", - params: { - unitId: 'AACBWAcof-611K4U', - vpaidMode: true // by default is 'false' + unitId: '1234abcd-5678efgh', } } ] @@ -159,18 +42,10 @@ Module that connects to ShowHeroes demand source to fetch bids. { bidder: "showheroes-bs", params: { - unitId: 'AACBTwsZVANd9NlB', - - outstreamOptions: { - // Required for the outstream renderer to exact node, one of - iframe: 'iframe_id', - // or - slot: 'slot_id' - } + unitId: '1234abcd-5678efgh', } } ] } ]; ``` - diff --git a/modules/sigmoidAnalyticsAdapter.js b/modules/sigmoidAnalyticsAdapter.js deleted file mode 100644 index a9d92b67e24..00000000000 --- a/modules/sigmoidAnalyticsAdapter.js +++ /dev/null @@ -1,293 +0,0 @@ -/* Sigmoid Analytics Adapter for prebid.js v1.1.0-pre -Updated : 2018-03-28 */ -import {includes} from '../src/polyfill.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {generateUUID, logError, logInfo} from '../src/utils.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const MODULE_CODE = 'sigmoid'; -const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); - -const url = 'https://kinesis.us-east-1.amazonaws.com/'; -const analyticsType = 'endpoint'; - -const auctionInitConst = EVENTS.AUCTION_INIT; -const auctionEndConst = EVENTS.AUCTION_END; -const bidWonConst = EVENTS.BID_WON; -const bidRequestConst = EVENTS.BID_REQUESTED; -const bidAdjustmentConst = EVENTS.BID_ADJUSTMENT; -const bidResponseConst = EVENTS.BID_RESPONSE; - -let initOptions = { publisherIds: [], utmTagData: [], adUnits: [] }; -let bidWon = {options: {}, events: []}; -let eventStack = {options: {}, events: []}; - -let auctionStatus = 'not_started'; - -let localStoragePrefix = 'sigmoid_analytics_'; -let utmTags = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content']; -let utmTimeoutKey = 'utm_timeout'; -let utmTimeout = 60 * 60 * 1000; -let sessionTimeout = 60 * 60 * 1000; -let sessionIdStorageKey = 'session_id'; -let sessionTimeoutKey = 'session_timeout'; - -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] : ''; -} - -function buildSessionIdLocalStorageKey() { - return localStoragePrefix.concat(sessionIdStorageKey); -} - -function buildSessionIdTimeoutLocalStorageKey() { - return localStoragePrefix.concat(sessionTimeoutKey); -} - -function updateSessionId() { - if (isSessionIdTimeoutExpired()) { - let newSessionId = generateUUID(); - storage.setDataInLocalStorage(buildSessionIdLocalStorageKey(), newSessionId); - } - initOptions.sessionId = getSessionId(); - updateSessionIdTimeout(); -} - -function updateSessionIdTimeout() { - storage.setDataInLocalStorage(buildSessionIdTimeoutLocalStorageKey(), Date.now()); -} - -function isSessionIdTimeoutExpired() { - let cpmSessionTimestamp = storage.getDataFromLocalStorage(buildSessionIdTimeoutLocalStorageKey()); - return Date.now() - cpmSessionTimestamp > sessionTimeout; -} - -function getSessionId() { - return storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) ? storage.getDataFromLocalStorage(buildSessionIdLocalStorageKey()) : ''; -} - -function updateUtmTimeout() { - storage.setDataInLocalStorage(buildUtmLocalStorageTimeoutKey(), Date.now()); -} - -function isUtmTimeoutExpired() { - let utmTimestamp = storage.getDataFromLocalStorage(buildUtmLocalStorageTimeoutKey()); - return (Date.now() - utmTimestamp) > utmTimeout; -} - -function buildUtmLocalStorageTimeoutKey() { - return localStoragePrefix.concat(utmTimeoutKey); -} - -function buildUtmLocalStorageKey(utmMarkKey) { - return localStoragePrefix.concat(utmMarkKey); -} - -function checkOptions() { - if (typeof initOptions.publisherIds === 'undefined') { - return false; - } - - return initOptions.publisherIds.length > 0; -} - -function checkAdUnitConfig() { - if (typeof initOptions.adUnits === 'undefined') { - return false; - } - - return initOptions.adUnits.length > 0; -} - -function buildBidWon(eventType, args) { - bidWon.options = initOptions; - if (checkAdUnitConfig()) { - if (includes(initOptions.adUnits, args.adUnitCode)) { - bidWon.events = [{ args: args, eventType: eventType }]; - } - } else { - bidWon.events = [{ args: args, eventType: eventType }]; - } -} - -function buildEventStack() { - eventStack.options = initOptions; -} - -function filterBidsByAdUnit(bids) { - var filteredBids = []; - bids.forEach(function (bid) { - if (includes(initOptions.adUnits, bid.placementCode)) { - filteredBids.push(bid); - } - }); - return filteredBids; -} - -function isValidEvent(eventType, adUnitCode) { - if (checkAdUnitConfig()) { - let validationEvents = [bidAdjustmentConst, bidResponseConst, bidWonConst]; - if (!includes(initOptions.adUnits, adUnitCode) && includes(validationEvents, eventType)) { - return false; - } - } - return true; -} - -function isValidEventStack() { - if (eventStack.events.length > 0) { - return eventStack.events.some(function(event) { - return bidRequestConst === event.eventType || bidWonConst === event.eventType; - }); - } - return false; -} - -function isValidBidWon() { - return bidWon.events.length > 0; -} - -function flushEventStack() { - eventStack.events = []; -} - -let sigmoidAdapter = Object.assign(adapter({url, analyticsType}), - { - track({eventType, args}) { - if (!checkOptions()) { - return; - } - - let info = Object.assign({}, args); - - if (info && info.ad) { - info.ad = ''; - } - - if (eventType === auctionInitConst) { - auctionStatus = 'started'; - } - - if (eventType === bidWonConst && auctionStatus === 'not_started') { - updateSessionId(); - buildBidWon(eventType, info); - if (isValidBidWon()) { - send(eventType, bidWon, 'bidWon'); - } - return; - } - - if (eventType === auctionEndConst) { - updateSessionId(); - buildEventStack(); - if (isValidEventStack()) { - send(eventType, eventStack, 'eventStack'); - } - auctionStatus = 'not_started'; - } else { - pushEvent(eventType, info); - } - }, - - }); - -sigmoidAdapter.originEnableAnalytics = sigmoidAdapter.enableAnalytics; - -sigmoidAdapter.enableAnalytics = function (config) { - initOptions = config.options; - initOptions.utmTagData = this.buildUtmTagData(); - logInfo('Sigmoid Analytics enabled with config', initOptions); - sigmoidAdapter.originEnableAnalytics(config); -}; - -sigmoidAdapter.buildUtmTagData = function () { - let utmTagData = {}; - let utmTagsDetected = false; - utmTags.forEach(function(utmTagKey) { - let utmTagValue = getParameterByName(utmTagKey); - if (utmTagValue !== '') { - utmTagsDetected = true; - } - utmTagData[utmTagKey] = utmTagValue; - }); - utmTags.forEach(function(utmTagKey) { - if (utmTagsDetected) { - storage.setDataInLocalStorage(buildUtmLocalStorageKey(utmTagKey), utmTagData[utmTagKey]); - updateUtmTimeout(); - } else { - if (!isUtmTimeoutExpired()) { - utmTagData[utmTagKey] = storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) ? storage.getDataFromLocalStorage(buildUtmLocalStorageKey(utmTagKey)) : ''; - updateUtmTimeout(); - } - } - }); - return utmTagData; -}; - -function send(eventType, data, sendDataType) { - // eslint-disable-next-line no-undef - AWS.config.credentials = new AWS.Credentials({ - accessKeyId: 'accesskey', secretAccessKey: 'secretkey' - }); - - // eslint-disable-next-line no-undef - AWS.config.region = 'us-east-1'; - // eslint-disable-next-line no-undef - AWS.config.credentials.get(function(err) { - // attach event listener - if (err) { - logError(err); - return; - } - // create kinesis service object - // eslint-disable-next-line no-undef - var kinesis = new AWS.Kinesis({ - apiVersion: '2013-12-02' - }); - var dataList = []; - var jsonData = {}; - jsonData['Data'] = JSON.stringify(data) + '\n'; - jsonData['PartitionKey'] = 'partition-' + Math.random().toString(36).substring(7); - dataList.push(jsonData); - kinesis.putRecords({ - Records: dataList, - StreamName: 'sample-stream' - }); - if (sendDataType === 'eventStack') { - flushEventStack(); - } - }); -}; - -function pushEvent(eventType, args) { - if (eventType === bidRequestConst) { - if (checkAdUnitConfig()) { - args.bids = filterBidsByAdUnit(args.bids); - } - if (args.bids.length > 0) { - eventStack.events.push({ eventType: eventType, args: args }); - } - } else { - if (isValidEvent(eventType, args.adUnitCode)) { - eventStack.events.push({ eventType: eventType, args: args }); - } - } -} - -adapterManager.registerAnalyticsAdapter({ - adapter: sigmoidAdapter, - code: MODULE_CODE, -}); - -export default sigmoidAdapter; diff --git a/modules/silverpushBidAdapter.js b/modules/silverpushBidAdapter.js index 5403f3bd88c..593e613d603 100644 --- a/modules/silverpushBidAdapter.js +++ b/modules/silverpushBidAdapter.js @@ -128,7 +128,7 @@ export const CONVERTER = ortbConverter({ }); return { bids: response.bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } else { return response.bids @@ -259,7 +259,7 @@ function getBidFloor(bid) { mediaType: '*', size: '*', }); - return bidFloor.floor; + return bidFloor?.floor; } function _renderer(bid) { diff --git a/modules/sirdataRtdProvider.js b/modules/sirdataRtdProvider.js index 29d10a3a071..129d708cf8f 100644 --- a/modules/sirdataRtdProvider.js +++ b/modules/sirdataRtdProvider.js @@ -7,21 +7,35 @@ * @module modules/sirdataRtdProvider * @requires module:modules/realTimeData */ -import {deepAccess, deepSetValue, isEmpty, logError, logInfo, mergeDeep} from '../src/utils.js'; -import {submodule} from '../src/hook.js'; -import {ajax} from '../src/ajax.js'; -import {findIndex} from '../src/polyfill.js'; -import {getRefererInfo} from '../src/refererDetection.js'; -import {config} from '../src/config.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import adapterManager from '../src/adapterManager.js'; +import { ajax, sendBeacon } from '../src/ajax.js'; +import { + deepAccess, checkCookieSupport, deepSetValue, hasDeviceAccess, inIframe, isEmpty, + logError, logInfo, mergeDeep +} from '../src/utils.js'; +import { findIndex } from '../src/polyfill.js'; +import { getGlobal } from '../src/prebidGlobal.js'; +import { getRefererInfo } from '../src/refererDetection.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; const SUBMODULE_NAME = 'SirdataRTDModule'; const ORTB2_NAME = 'sirdata.com'; const LOG_PREFIX = 'Sirdata RTD: '; +const EUIDS_STORAGE_NAME = 'SDDAN'; +// Get the cookie domain from the referer info or fallback to window location hostname +const cookieDomain = getRefererInfo().domain || window.location.hostname; -const partnerIds = { +/** @type {number} */ +const GVLID = 53; + +/** @type {object} */ +const STORAGE = getStorageManager({ moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME }); +const bidderAliasRegistry = adapterManager.aliasRegistry || {}; +const biddersId = { // Partner IDs mapping for different SSPs and DSPs 'criteo': 27443, 'openx': 30342, 'pubmatic': 30345, @@ -30,25 +44,9 @@ const partnerIds = { 'yahoossp': 30339, 'rubicon': 27452, 'appnexus': 27446, - 'appnexusAst': 27446, - 'brealtime': 27446, - 'emetriq': 27446, - 'emxdigital': 27446, - 'pagescience': 27446, 'gourmetads': 33394, - 'matomy': 27446, - 'featureforward': 27446, - 'oftmedia': 27446, - 'districtm': 27446, - 'adasta': 27446, - 'beintoo': 27446, - 'gravity': 27446, - 'msq_classic': 27878, - 'msq_max': 27878, - '366_apx': 27878, 'mediasquare': 27878, 'smartadserver': 27440, - 'smart': 27440, 'proxistore': 27484, 'ix': 27248, 'sdRtdForGpt': 27449, @@ -65,57 +63,367 @@ const partnerIds = { 'zeta_global_ssp': 33385, }; -export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { - logInfo(LOG_PREFIX, 'init'); - moduleConfig.params = moduleConfig.params || {}; - - let tcString = (userConsent && userConsent.gdpr && userConsent.gdpr.consentString ? userConsent.gdpr.consentString : ''); - let 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; - moduleConfig.params.actualUrl = moduleConfig.params.actualUrl || null; - - let sirdataDomain; - let 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 (config.getConfig('consentManagement.gdpr')) { - // Default endpoint for Contextual results only 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; +const eidsProvidersMap = { + 'id5': 'id5-sync.com', + 'id5id': 'id5-sync.com', + 'id5_id': 'id5-sync.com', + 'pubprovided_id': 'pubProvidedId', + 'ppid': 'pubProvidedId', + 'first-id.fr': 'pubProvidedId', + 'sharedid': 'pubcid.org', + 'publishercommonid': 'pubcid.org', + 'pubcid.org': 'pubcid.org', +} + +// params +let params = { + partnerId: 1, + key: 1, + actualUrl: getRefererInfo().stack.pop() || getRefererInfo().page, + cookieAccessGranted: false, + setGptKeyValues: true, + contextualMinRelevancyScore: 30, + preprod: false, + authorizedEids: ['pubProvidedId', 'id5-sync.com', 'pubcid.org'], + avoidPostContent: false, + sirdataDomain: 'cookieless-data.com', + bidders: [] +}; + +/** + * Sets a cookie on the top-level domain + * @param {string} key - The cookie name + * @param {string} value - The cookie value + * @param {string} hostname - The hostname for setting the cookie + * @param {boolean} deleteCookie - The cookie must be deleted + * @returns {boolean} - True if the cookie was successfully set, otherwise false + */ +export function setCookieOnTopDomain(key, value, hostname, deleteCookie) { + const subDomains = hostname.split('.'); + let expTime = new Date(); + expTime.setTime(expTime.getTime() + (deleteCookie ? -1 : 365 * 24 * 60 * 60 * 1000)); // Set expiration time + for (let i = 0; i < subDomains.length; ++i) { + // Try to write the cookie on this subdomain (we want it to be stored only on the TLD+1) + const domain = subDomains.slice(subDomains.length - i - 1).join('.'); + try { + STORAGE.setCookie(key, value, expTime.toUTCString(), 'Lax', '.' + domain); + // Try to read the cookie to check if we wrote it + if (STORAGE.getCookie(key, null) === value) return true; // Check if the cookie was set, and if so top domain was found. If deletion with expire date -1 will parse until complete host + } catch (e) { + logError(LOG_PREFIX, e); + } + } + return false; +} + +/** + * Retrieves the UID from storage (cookies or local storage) + * @returns {Array|null} - Array of UID objects or null if no UID found + */ +export function getUidFromStorage() { + let cUid = STORAGE.getCookie(EUIDS_STORAGE_NAME, null); + let lsUid = STORAGE.getDataFromLocalStorage(EUIDS_STORAGE_NAME, null); + if (cUid && (!lsUid || cUid !== lsUid)) { + STORAGE.setDataInLocalStorage(EUIDS_STORAGE_NAME, cUid, null); + } else if (lsUid && !cUid) { + setCookieOnTopDomain(EUIDS_STORAGE_NAME, lsUid, cookieDomain, false); + cUid = lsUid; + } + return cUid ? [{ source: 'sddan.com', uids: [{ id: cUid, atype: 1 }] }] : null; +} + +/** + * Sets the UID in storage (cookies and local storage) + * @param {string} sddanId - The UID to be set + * @returns {boolean} - True if the UID was successfully set, otherwise false + */ +export function setUidInStorage(sddanId) { + if (!sddanId) return false; + sddanId = encodeURI(sddanId.toString()); + setCookieOnTopDomain(EUIDS_STORAGE_NAME, sddanId, cookieDomain, false); + STORAGE.setDataInLocalStorage(EUIDS_STORAGE_NAME, sddanId, null); + return true; +} + +/** + * Merges and cleans objects from two eids arrays based on the 'source' and unique 'id' within the 'uids' array. + * Processes each array to add unique items or merge uids if the source already exists. + * @param {Array} euids1 - The first array to process and merge. + * @param {Array} euids2 - The second array to process and merge. + * @returns {Array} The merged array with unique sources and uid ids. + */ +export function mergeEuidsArrays(euids1, euids2) { + if (isEmpty(euids1)) return euids2; + if (isEmpty(euids2)) return euids1; + const mergedArray = []; + // Helper function to process each array + const processArray = (array) => { + array.forEach(item => { + if (item.uids) { + const foundIndex = findIndex(mergedArray, function (x) { + return x.source === item.source; + }); + if (foundIndex !== -1) { + // Merge uids if the source is found + item.uids.forEach(uid => { + if (!mergedArray[foundIndex].uids.some(u => u.id === uid.id)) { + mergedArray[foundIndex].uids.push(uid); + } + }); + } else { + // Add the entire item if the source does not exist + mergedArray.push({ ...item, uids: [...item.uids] }); + } + } + }); + }; + // Process both euids1 and euids2 + processArray(euids1); + processArray(euids2); + return mergedArray; +} + +/** + * Handles data deletion request by removing stored EU IDs + * @param {Object} moduleConfig - The module configuration + * @returns {boolean} - True if data was deleted successfully + */ +export function onDataDeletionRequest(moduleConfig) { + if (moduleConfig && moduleConfig.params) { + setCookieOnTopDomain(EUIDS_STORAGE_NAME, '', window.location.hostname, true); + STORAGE.removeDataFromLocalStorage(EUIDS_STORAGE_NAME, null); + } + return !getUidFromStorage(); +} + +/** + * Sends the page content for semantic analysis to Sirdata's server. + * @param {string} postContentToken - The token required to post content. + * @param {string} actualUrl - The actual URL of the current page. + * @returns {boolean} - True if the content was sent successfully + */ +export function postContentForSemanticAnalysis(postContentToken, actualUrl) { + if (!postContentToken || !actualUrl) return false; + + try { + let content = document.implementation.createHTMLDocument(''); + // Clone the current document content to avoid altering the original page content + content.documentElement.innerHTML = document.documentElement.innerHTML; + // Sanitize the cloned content to remove unnecessary elements and PII + content = sanitizeContent(content); + // Serialize the sanitized content to a string + const payload = new XMLSerializer().serializeToString(content.documentElement); + + if (payload && payload.length > 300 && payload.length < 300000) { + const url = `https://contextual.sirdata.io/api/v1/push/contextual?post_content_token=${postContentToken}&url=${encodeURIComponent(actualUrl)}`; + + if (!sendBeacon(url, payload)) { + // Fallback to using AJAX if Beacon API is not supported + ajax(url, {}, payload, { + contentType: 'text/plain', + method: 'POST', + withCredentials: false, // No user-specific data is tied to the request + referrerPolicy: 'unsafe-url', + crossOrigin: true + }); + } + } + } catch (e) { + logError(LOG_PREFIX, e); + return false; } + return true; +} + +/** + * Executes a callback function when the document is fully loaded. + * @param {function} callback - The function to execute when the document is ready. + */ +export function onDocumentReady(callback) { + if (typeof callback !== 'function') return false; + try { + if (document.readyState && document.readyState !== 'loading') { + callback(); + } else if (typeof document.addEventListener === 'function') { + document.addEventListener('DOMContentLoaded', callback); + } + } catch (e) { + callback(); + } + return true; +} - // default global endpoint is cookie-based if no rules falls into cookieless or consent has been given or GDPR doesn't apply +/** + * Removes Personally Identifiable Information (PII) from the content + * @param {string} content - The content to be sanitized + * @returns {string} - The sanitized content + */ +export function removePII(content) { + const patterns = [ + /\b(?:\d{4}[ -]?){3}\d{4}\b/g, // Credit card numbers + /\b\d{10,12}\b/g, // US bank account numbers + /\b\d{5}\d{5}\d{11}\d{2}\b/g, // EU bank account numbers + /\b(\d{3}-\d{2}-\d{4}|\d{9}|\d{13}|\d{2} \d{2} \d{2} \d{3} \d{3} \d{3})\b/g, // SSN + /\b[A-Z]{1,2}\d{6,9}\b/g, // Passport numbers + /\b(\d{8,10}|\d{3}-\d{3}-\d{3}-\d{3}|\d{2} \d{2} \d{2} \d{3} \d{3})\b/g, // ID card numbers + /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b/g, // Email addresses + /(\+?\d{1,3}[-.\s]?)?(\(?\d{2,3}\)?[-.\s]?)(\d{2}[-.\s]?){3,4}\d{2}/g // Phone numbers + ]; + patterns.forEach(pattern => { + content = content.replace(pattern, ''); + }); + return content; +} - if (!sirdataDomain || !gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[53] && userConsent.gdpr.vendorData.purpose.consents[1] && userConsent.gdpr.vendorData.purpose.consents[4])) { - sirdataDomain = 'sddan.com'; - sendWithCredentials = true; +/** + * Sanitizes the content by removing unnecessary elements and PII + * @param {Object} content - The content to be sanitized + * @returns {Object} - The sanitized content + */ +export function sanitizeContent(content) { + if (content && content.documentElement.textContent && content.documentElement.textContent.length > 500) { + // Reduce size by removing useless content + // Allowed tags + const allowedTags = [ + 'div', 'span', 'a', 'article', 'section', 'p', 'h1', 'h2', 'body', 'b', 'u', 'i', 'big', 'mark', 'ol', 'small', 'strong', 'blockquote', + 'nav', 'menu', 'li', 'ul', 'ins', 'head', 'title', 'main', 'var', 'table', 'caption', 'colgroup', 'col', 'tr', 'td', 'th', + 'summary', 'details', 'dl', 'dt', 'dd' + ]; + + const processElement = (element) => { + Array.from(element.childNodes).reverse().forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE) { + processElement(child); + Array.from(child.attributes).forEach(attr => { + // keeps only id attributes and class attribute if useful for contextualisation + if (attr.name === 'class' && !/^(main|article|product)/.test(attr.value)) { + child.removeAttribute(attr.name); + } else if (attr.name !== 'id') { + child.removeAttribute(attr.name); + } + }); + if (!child.innerHTML.trim() || !allowedTags.includes(child.tagName.toLowerCase())) { // Keeps only allowed Tags (allowedTags) + child.remove(); + } + } + }); + }; + + const removeEmpty = (element) => { // remove empty tags + Array.from(element.childNodes).reverse().forEach(child => { + if (child.nodeType === Node.ELEMENT_NODE) { + removeEmpty(child); + if (!child.innerHTML.trim()) { + child.remove(); + } + } else if (child.nodeType === Node.TEXT_NODE && !child.textContent.trim()) { + child.remove(); + } + }); + }; + + processElement(content.documentElement); + removeEmpty(content.documentElement); + + // Clean any potential PII + content.documentElement.innerHTML = removePII(content.documentElement.innerHTML); + + let htmlContent = content.documentElement.innerHTML; + // Remove HTML comments + // This regex removes HTML comments, including those that might not be properly closed + htmlContent = htmlContent.replace(/|$)/g, ''); + // Remove multiple spaces + htmlContent = htmlContent.replace(/\s+/g, ' '); + // Remove spaces between tags + htmlContent = htmlContent.replace(/>\s+<'); + // Assign the cleaned content + content.documentElement.innerHTML = htmlContent; + } + return content; +} + +/** + * Fetches segments and categories from Sirdata server and processes the response + * @param {Object} reqBidsConfigObj - The bids configuration object + * @param {function} onDone - The callback function to be called upon completion + * @param {Object} moduleConfig - The module Config + * @param {Object} userConsent - The user consent information + */ +export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, userConsent) { + logInfo(LOG_PREFIX, 'get Segments And Categories'); + const adUnits = (reqBidsConfigObj && reqBidsConfigObj.adUnits) || getGlobal().adUnits; + if (!adUnits) { + logInfo(LOG_PREFIX, 'no ad unit, RTD processing is useless'); + onDone(); + return; } - let actualUrl = moduleConfig.params.actualUrl || getRefererInfo().stack.pop() || getRefererInfo().page; + const gdprApplies = deepAccess(userConsent, 'gdpr.gdprApplies') ? userConsent.gdpr.gdprApplies : false; + const sirdataSubDomain = params.preprod ? 'kvt-preprod' : 'kvt'; + + let euids; // empty if no right to access device (publisher or user reject) + let privacySignals = ''; + + // Default global endpoint is cookie-based only if no rules falls into cookieless or consent has been given or GDPR doesn't apply + if (hasDeviceAccess() && !userConsent.coppa && (isEmpty(userConsent.usp) || userConsent.usp === -1 || (userConsent.usp[0] === '1' && (userConsent.usp[1] !== 'N' && userConsent.usp[2] !== 'Y'))) && (!gdprApplies || (deepAccess(userConsent, 'gdpr.vendorData.vendor.consents') && userConsent.gdpr.vendorData.vendor.consents[GVLID] && deepAccess(userConsent, 'gdpr.vendorData.purpose.consents') && userConsent.gdpr.vendorData.purpose.consents[1] && (userConsent.gdpr.vendorData.purpose.consents[2] || userConsent.gdpr.vendorData.purpose.consents[3]) && userConsent.gdpr.vendorData.purpose.consents[4])) && (isEmpty(userConsent.gpp) || userConsent.gpp.gppString) && checkCookieSupport()) { + params.sirdataDomain = 'sddan.com'; // cookie based domain + params.cookieAccessGranted = true; // cookies sent in request + + if (gdprApplies && deepAccess(userConsent, 'gdpr.consentString')) { + privacySignals = `&gdpr=${gdprApplies}&gdpr_consent=${userConsent.gdpr.consentString}`; + } else if (!isEmpty(userConsent.usp)) { + privacySignals = `&ccpa_consent=${userConsent.usp.toString()}`; + } else if (deepAccess(userConsent, 'gpp.gppString')) { + const sid = deepAccess(userConsent, 'gpp.applicableSections') ? `&gpp_sid=${userConsent.gpp.applicableSections.join(',')}` : ''; + privacySignals = `&gpp=${userConsent.gpp.gppString}${sid}`; + } + + // Authorized EUIDS from storage and sync global for graph + euids = getUidFromStorage(); // Sirdata Id + + if (!isEmpty(params.authorizedEids) && typeof getGlobal().getUserIds === 'function') { + let filteredEids = {}; + const authorizedEids = params.authorizedEids; + const globalUserIds = getGlobal().getUserIds(); + const globalUserIdsAsEids = getGlobal().getUserIdsAsEids(); + + const hasPubProvidedId = authorizedEids.indexOf('pubProvidedId') !== -1; + + if (hasPubProvidedId && !isEmpty(globalUserIds.pubProvidedId)) { // Publisher allows pubProvidedId + filteredEids = mergeEuidsArrays(filteredEids, globalUserIds.pubProvidedId); + } + + if (!hasPubProvidedId || authorizedEids.length > 1) { // Publisher allows other Id providers + const filteredGlobalEids = globalUserIdsAsEids.filter(entry => authorizedEids.includes(entry.source)); + if (!isEmpty(filteredGlobalEids)) { + filteredEids = mergeEuidsArrays(filteredEids, filteredGlobalEids); + } + } + + if (!isEmpty(filteredEids)) { + euids = mergeEuidsArrays(euids, filteredEids); // merge ids for graph id + } + } + } - 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=' + encodeURIComponent(actualUrl) : ''); + const url = `https://${sirdataSubDomain}.${params.sirdataDomain}/api/v1/public/p/${params.partnerId.toString()}/d/${params.key.toString()}/s?callback=&allowed_post_content=${!params.avoidPostContent}${privacySignals}${params.actualUrl ? `&url=${encodeURIComponent(params.actualUrl)}` : ''}`; + const method = isEmpty(euids) ? 'GET' : 'POST'; + const payload = isEmpty(euids) ? null : JSON.stringify({ external_ids: euids }); - ajax(url, - { + try { + ajax(url, { success: function (response, req) { if (req.status === 200) { try { const data = JSON.parse(response); if (data && data.segments) { - addSegmentData(reqBidsConfigObj, data, moduleConfig, onDone); + addSegmentData(reqBidsConfigObj, data, adUnits, onDone); } else { onDone(); } } catch (e) { onDone(); - logError('unable to parse Sirdata data' + e); + logError(LOG_PREFIX, 'unable to parse Sirdata data' + e); } } else if (req.status === 204) { onDone(); @@ -123,104 +431,136 @@ export function getSegmentsAndCategories(reqBidsConfigObj, onDone, moduleConfig, }, error: function () { onDone(); - logError('unable to get Sirdata data'); + logError(LOG_PREFIX, 'unable to get Sirdata data'); } - }, - null, - { + }, payload, { contentType: 'text/plain', - method: 'GET', - withCredentials: sendWithCredentials, + method: method, + withCredentials: params.cookieAccessGranted, referrerPolicy: 'unsafe-url', crossOrigin: true }); + } catch (e) { + logError(LOG_PREFIX, e); + } } +/** + * Pushes data to OpenRTB 2.5 fragments for the specified bidder + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {Object} data - The data to be pushed + * @param {number} segtaxid - The segment taxonomy ID + * @param {number} cattaxid - The category taxonomy ID + * @returns {boolean} - True if data was pushed successfully + */ export function pushToOrtb2(ortb2Fragments, bidder, data, segtaxid, cattaxid) { try { if (!isEmpty(data.segments)) { if (segtaxid) { setOrtb2Sda(ortb2Fragments, bidder, 'user', data.segments, segtaxid); } else { - setOrtb2(ortb2Fragments, bidder, 'user.ext.data', {sd_rtd: {segments: data.segments}}); + setOrtb2(ortb2Fragments, bidder, 'user.ext.data', { sd_rtd: { segments: data.segments } }); } } if (!isEmpty(data.categories)) { if (cattaxid) { setOrtb2Sda(ortb2Fragments, bidder, 'site', data.categories, cattaxid); } else { - setOrtb2(ortb2Fragments, bidder, 'site.ext.data', {sd_rtd: {categories: data.categories}}); + setOrtb2(ortb2Fragments, bidder, 'site.ext.data', { sd_rtd: { categories: data.categories } }); } } if (!isEmpty(data.categories_score) && !cattaxid) { - setOrtb2(ortb2Fragments, bidder, 'site.ext.data', {sd_rtd: {categories_score: data.categories_score}}); + setOrtb2(ortb2Fragments, bidder, 'site.ext.data', { sd_rtd: { categories_score: data.categories_score } }); } } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } return true; } +/** + * Sets OpenRTB 2.5 Seller Defined Audiences (SDA) data + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {string} type - The type of data ('user' or 'site') + * @param {Array} segments - The segments to be set + * @param {number} segtaxValue - The segment taxonomy value + * @returns {boolean} - True if data was set successfully + */ export function setOrtb2Sda(ortb2Fragments, bidder, type, segments, segtaxValue) { try { - let ortb2Data = [{ - name: ORTB2_NAME, - segment: segments.map((segmentId) => ({ id: segmentId })), - }]; - if (segtaxValue) { - ortb2Data[0].ext = { segtax: segtaxValue }; - } - let ortb2Conf = (type === 'site' ? {site: {content: {data: ortb2Data}}} : {user: {data: ortb2Data}}); - if (bidder) { - ortb2Conf = {[bidder]: ortb2Conf}; - } + let ortb2Data = [{ name: ORTB2_NAME, segment: segments.map(segmentId => ({ id: segmentId })) }]; + if (segtaxValue) ortb2Data[0].ext = { segtax: segtaxValue }; + let ortb2Conf = (type === 'site') ? { site: { content: { data: ortb2Data } } } : { user: { data: ortb2Data } }; + if (bidder) ortb2Conf = { [bidder]: ortb2Conf }; mergeDeep(ortb2Fragments, ortb2Conf); } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } return true; } +/** + * Sets OpenRTB 2.5 data at the specified path + * @param {Object} ortb2Fragments - The OpenRTB 2.5 fragments + * @param {string} bidder - The bidder name + * @param {string} path - The path to set the data at + * @param {Object} segments - The segments to be set + * @returns {boolean} - True if data was set successfully + */ export function setOrtb2(ortb2Fragments, bidder, path, segments) { try { - if (isEmpty(segments)) { return false; } + if (isEmpty(segments)) return false; let ortb2Conf = {}; - deepSetValue(ortb2Conf, path, segments || {}); - if (bidder) { - ortb2Conf = {[bidder]: ortb2Conf}; - } + deepSetValue(ortb2Conf, path, segments); + if (bidder) ortb2Conf = { [bidder]: ortb2Conf }; mergeDeep(ortb2Fragments, ortb2Conf); } catch (e) { - logError(e) + logError(LOG_PREFIX, e); } - return true; } +/** + * Loads a custom function for processing ad unit data + * @param {function} todo - The custom function to be executed + * @param {Object} adUnit - The ad unit object + * @param {Object} list - The list of data + * @param {Object} data - The data object + * @param {Object} bid - The bid object + * @returns {boolean} - True if the function was executed successfully + */ export function loadCustomFunction(todo, adUnit, list, data, bid) { try { - if (typeof todo == 'function') { - todo(adUnit, list, data, bid); - } + if (typeof todo === 'function') todo(adUnit, list, data, bid); } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } return true; } +/** + * Gets segments and categories array from the data object + * @param {Object} data - The data object + * @param {number} minScore - The minimum score threshold for contextual relevancy + * @param {string} pid - The partner ID (attributed by Sirdata to bidder) + * @returns {Object} - The segments and categories data + */ export function getSegAndCatsArray(data, minScore, pid) { - let sirdataData = {'segments': [], 'categories': [], 'categories_score': {}}; - minScore = minScore && typeof minScore == 'number' ? minScore : 30; - let cattaxid = data.cattaxid || null; - let segtaxid = data.segtaxid || null; + let sirdataData = { segments: [], categories: [], categories_score: {} }; + minScore = typeof minScore === 'number' ? minScore : 30; + const { cattaxid, segtaxid, segments } = data; + const contextualCategories = data.contextual_categories || {}; + // parses contextual categories try { - if (data && data.contextual_categories) { - for (let catId in data.contextual_categories) { - if (data.contextual_categories.hasOwnProperty(catId) && data.contextual_categories[catId]) { - let value = data.contextual_categories[catId]; - if (value >= minScore && sirdataData.categories.indexOf(catId) === -1) { - if (pid && pid === '27440' && cattaxid) { // Equativ only - sirdataData.categories.push(pid.toString() + 'cc' + catId.toString()); + if (contextualCategories) { + for (let catId in contextualCategories) { + if (contextualCategories.hasOwnProperty(catId) && contextualCategories[catId]) { + let value = contextualCategories[catId]; + if (value >= minScore && !sirdataData.categories.includes(catId)) { + if (pid === '27440' && cattaxid) { // Equativ only + sirdataData.categories.push(`${pid}cc${catId}`); } else { sirdataData.categories.push(catId.toString()); sirdataData.categories_score[catId] = value; @@ -230,15 +570,16 @@ export function getSegAndCatsArray(data, minScore, pid) { } } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } + // parses user-centric segments (empty if no right to access device/process PII) try { - if (data && data.segments) { - for (let segId in data.segments) { - if (data.segments.hasOwnProperty(segId) && data.segments[segId]) { - let id = data.segments[segId].toString(); - if (pid && pid === '27440' && segtaxid) { // Equativ only - sirdataData.segments.push(pid.toString() + 'us' + id); + if (segments) { + for (let segId in segments) { + if (segments.hasOwnProperty(segId) && segments[segId]) { + let id = segments[segId].toString(); + if (pid === '27440' && segtaxid) { // Equativ only + sirdataData.segments.push(`${pid}us${id}`); } else { sirdataData.segments.push(id); } @@ -246,150 +587,177 @@ export function getSegAndCatsArray(data, minScore, pid) { } } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } return sirdataData; } -export function applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit) { - // only share SDA data if whitelisted - if (!biddersParamsExist || indexFound) { +/** + * Applies Seller Defined Audience (SDA) data and specific data for the bidder + * @param {Object} data - The data object + * @param {Object} sirdataData - The Sirdata data object + * @param {boolean} biddersParamsExist - Flag indicating if bidder parameters exist + * @param {Object} reqBids - The request bids object + * @param {Object} bid - The bid object + * @param {number} bidderIndex - The bidder index + * @param {Object} adUnit - The ad unit object + * @param {string} aliasActualBidder - The bidder Alias + * @returns {Object} - The modified Sirdata data + */ +export function applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder) { + // Apply custom function or return Bidder Specific Data if publisher is ok + if (bidderIndex && params.bidders[bidderIndex]?.customFunction && typeof (params.bidders[bidderIndex]?.customFunction) === 'function') { + return loadCustomFunction(params.bidders[bidderIndex].customFunction, adUnit, sirdataData, data, bid); + } + + // Only share Publisher SDA data if whitelisted + if (!biddersParamsExist || bidderIndex) { // SDA Publisher - let sirdataDataForSDA = getSegAndCatsArray(data, minScore, moduleConfig.params.partnerId.toString()); + let sirdataDataForSDA = getSegAndCatsArray(data, params.contextualMinRelevancyScore, params.partnerId.toString()); pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, sirdataDataForSDA, data.segtaxid, data.cattaxid); } - // always share SDA for curation - let curationId = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('curationId') ? moduleConfig.params.bidders[bidderIndex].curationId : (partnerIds[bid.bidder] ? partnerIds[bid.bidder] : null)); - if (curationId) { - // seller defined audience & bidder specific data - if (data.shared_taxonomy && data.shared_taxonomy[curationId]) { - // Get Bidder Specific Data - let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], minScore, curationId.toString()); - pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, curationData, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + // Always share SDA for curation + if (!isEmpty(data.shared_taxonomy)) { + let curationId = (bidderIndex && params.bidders[bidderIndex]?.curationId) || biddersId[aliasActualBidder]; + if (curationId && data.shared_taxonomy[curationId]) { + // Seller defined audience & bidder specific data + let curationData = getSegAndCatsArray(data.shared_taxonomy[curationId], params.contextualMinRelevancyScore, curationId.toString()); + if (!isEmpty(curationData)) { + pushToOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, curationData, data.shared_taxonomy[curationId].segtaxid, data.shared_taxonomy[curationId].cattaxid); + mergeDeep(sirdataData, curationData); + } } } - // Apply custom function or return Bidder Specific Data if publisher is ok - if (!biddersParamsExist || indexFound) { - if (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('customFunction')) { - return loadCustomFunction(moduleConfig.params.bidders[bidderIndex].customFunction, adUnit, sirdataData, data, bid); - } else { - return sirdataData; - } - } + return sirdataData; } -export function addSegmentData(reqBids, data, moduleConfig, onDone) { - const adUnits = (reqBids && reqBids.adUnits) || getGlobal().adUnits; - if (!adUnits) { - onDone(); - return; - } - - moduleConfig = moduleConfig || {}; - moduleConfig.params = moduleConfig.params || {}; - const globalMinScore = moduleConfig.params.hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.contextualMinRelevancyScore : 30; - let sirdataData = getSegAndCatsArray(data, globalMinScore, null); +/** + * Adds segment data to the request bids object and processes the data + * @param {Object} reqBids - The request bids object + * @param {Object} data - The data object + * @param {Array} adUnits - The ad units array + * @param {function} onDone - The callback function to be called upon completion + * @returns {Array} - The ad units array + */ +export function addSegmentData(reqBids, data, adUnits, onDone) { + logInfo(LOG_PREFIX, 'Dispatch Segments And Categories'); + const minScore = params.contextualMinRelevancyScore || 30; + let sirdataData = getSegAndCatsArray(data, minScore, ''); - const biddersParamsExist = (!!(moduleConfig.params && moduleConfig.params.bidders && moduleConfig.params.bidders.length > 0)); + const biddersParamsExist = params.bidders.length > 0; // Global ortb2 SDA - if (data.global_taxonomy && !isEmpty(data.global_taxonomy)) { - let globalData = {'segments': [], 'categories': [], 'categories_score': []}; + if (!isEmpty(data.global_taxonomy)) { for (let i in data.global_taxonomy) { + let globalData; if (!isEmpty(data.global_taxonomy[i])) { - globalData = getSegAndCatsArray(data.global_taxonomy[i], globalMinScore, null); - pushToOrtb2(reqBids.ortb2Fragments?.global, null, globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + globalData = getSegAndCatsArray(data.global_taxonomy[i], params.contextualMinRelevancyScore, ''); + if (!isEmpty(globalData)) { + pushToOrtb2(reqBids.ortb2Fragments?.global, '', globalData, data.global_taxonomy[i].segtaxid, data.global_taxonomy[i].cattaxid); + } } } } // Google targeting - if (typeof window.googletag !== 'undefined' && (moduleConfig.params.setGptKeyValues || !moduleConfig.params.hasOwnProperty('setGptKeyValues'))) { + if (typeof window.googletag !== 'undefined' && params.setGptKeyValues) { try { - let gptCurationId = (moduleConfig.params.gptCurationId ? moduleConfig.params.gptCurationId : (partnerIds['sdRtdForGpt'] ? partnerIds['sdRtdForGpt'] : null)); - let sirdataMergedList = []; - if (gptCurationId && data.shared_taxonomy && data.shared_taxonomy[gptCurationId]) { - let gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], globalMinScore, null); - sirdataMergedList = sirdataMergedList.concat(gamCurationData.segments).concat(gamCurationData.categories); - } else { - sirdataMergedList = sirdataData.segments.concat(sirdataData.categories); + const gptCurationId = params.gptCurationId || biddersId.sdRtdForGpt; + let sirdataMergedList = [...sirdataData.segments, ...sirdataData.categories]; + + if (gptCurationId && data.shared_taxonomy?.[gptCurationId]) { + const gamCurationData = getSegAndCatsArray(data.shared_taxonomy[gptCurationId], params.contextualMinRelevancyScore, ''); + sirdataMergedList = [...sirdataMergedList, ...gamCurationData.segments, ...gamCurationData.categories]; } - window.googletag.cmd.push(function() { - window.googletag.pubads().getSlots().forEach(function (n) { - if (typeof n.setTargeting !== 'undefined' && sirdataMergedList && sirdataMergedList.length > 0) { - n.setTargeting('sd_rtd', sirdataMergedList); + + window.googletag.cmd.push(() => { + window.googletag.pubads().getSlots().forEach(slot => { + if (typeof slot.setTargeting !== 'undefined' && sirdataMergedList.length > 0) { + slot.setTargeting('sd_rtd', sirdataMergedList); } }); }); } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } } - // Bid targeting level for FPD non-generic biders - let bidderIndex = ''; - let indexFound = false; - adUnits.forEach(adUnit => { - 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)); + return adUnit.bids?.forEach(bid => { + const bidderIndex = findIndex(params.bidders, function (i) { return i.bidder === bid.bidder; }); try { - let minScore = (indexFound && moduleConfig.params.bidders[bidderIndex].hasOwnProperty('contextualMinRelevancyScore') ? moduleConfig.params.bidders[bidderIndex].contextualMinRelevancyScore : globalMinScore); - switch (bid.bidder) { - case 'appnexus': - case 'appnexusAst': - case 'brealtime': - case 'emetriq': - 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': - sirdataData = applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); - if (sirdataData.segments && sirdataData.segments.length > 0) { - setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.keywords', 'sd_rtd=' + sirdataData.segments.join(',sd_rtd=')); - } - if (sirdataData.categories && sirdataData.categories.length > 0) { - setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'site.content.keywords', 'sd_rtd=' + sirdataData.categories.join(',sd_rtd=')); - } - break; - - default: - if (!biddersParamsExist || (indexFound && (!moduleConfig.params.bidders[bidderIndex].hasOwnProperty('adUnitCodes') || moduleConfig.params.bidders[bidderIndex].adUnitCodes.indexOf(adUnit.code) !== -1))) { - applySdaGetSpecificData(data, sirdataData, biddersParamsExist, minScore, reqBids, bid, moduleConfig, indexFound, bidderIndex, adUnit); - } + const aliasActualBidder = bidderAliasRegistry[bid.bidder] || bid.bidder; + if (aliasActualBidder === 'appnexus') { + let xandrData = applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder); + // Surprisingly, to date Xandr doesn't support SDA, we need to set specific 'keywords' entries + if (xandrData.segments.length > 0) { + setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'user.keywords', `sd_rtd=${xandrData.segments.join(',sd_rtd=')}`); + } + if (xandrData.categories.length > 0) { + setOrtb2(reqBids.ortb2Fragments?.bidder, bid.bidder, 'site.content.keywords', `sd_rtd=${xandrData.categories.join(',sd_rtd=')}`); + } + } else { + applySdaGetSpecificData(data, sirdataData, biddersParamsExist, reqBids, bid, bidderIndex, adUnit, aliasActualBidder); } } catch (e) { - logError(e); + logError(LOG_PREFIX, e); } - }) + }); }); + + // Trigger onDone onDone(); + + // Postprocessing: should we send async content to categorize content and/or store Sirdata ID (stored in 3PC) within local scope ? + if (params.sirdataDomain === 'sddan.com') { // Means consent has been given for device and content access + if (!isEmpty(data.sddan_id) && params.cookieAccessGranted) { // Device access allowed by publisher and cookies are supported + setUidInStorage(data.sddan_id); // Save Sirdata user ID + } + if (!params.avoidPostContent && params.actualUrl && !inIframe() && !isEmpty(data.post_content_token)) { + onDocumentReady(() => postContentForSemanticAnalysis(data.post_content_token, params.actualUrl)); + } + } return adUnits; } -export function init(config) { - logInfo(LOG_PREFIX, config); +/** + * Initializes the module with the given configuration + * @param {Object} moduleConfig - The module configuration + * @returns {boolean} - True if the initialization was successful + */ +export function init(moduleConfig) { + logInfo(LOG_PREFIX, moduleConfig); + if (typeof (moduleConfig.params) !== 'object' || !moduleConfig.params.key) return false; + if (typeof (moduleConfig.params.authorizedEids) !== 'object' || !Array.isArray(moduleConfig.params.authorizedEids)) { + delete (moduleConfig.params.authorizedEids); // must be array of strings + } else { + // we need it if the publishers uses user Id module name instead of key or source + const resultSet = new Set( + moduleConfig.params.authorizedEids.map(item => { + const formattedItem = item.toLowerCase().replace(/\s+/g, '_'); // Normalize + return eidsProvidersMap[formattedItem] || formattedItem; + }) + ); + moduleConfig.params.authorizedEids = Array.from(resultSet); + } + if (typeof (moduleConfig.params.bidders) !== 'object' || !Array.isArray(moduleConfig.params.bidders)) delete (moduleConfig.params.bidders); // must be array of objects + delete (moduleConfig.params.sirdataDomain); // delete cookieless domain if specified => shouldn't be overridden by publisher + params = Object.assign({}, params, moduleConfig.params); return true; } +/** + * The Sirdata submodule definition + * @type {Object} + */ export const sirdataSubmodule = { name: SUBMODULE_NAME, + gvlid: GVLID, init: init, getBidRequestData: getSegmentsAndCategories }; +// Register the Sirdata submodule with the real time data module submodule(MODULE_NAME, sirdataSubmodule); diff --git a/modules/sirdataRtdProvider.md b/modules/sirdataRtdProvider.md index f67e34db43a..9e712e5fbd6 100644 --- a/modules/sirdataRtdProvider.md +++ b/modules/sirdataRtdProvider.md @@ -1,30 +1,28 @@ -# Sirdata Real-Time Data Submodule +# Sirdata RTD/SDA Module -Module Name: Sirdata Rtd Provider +Module Name: Sirdata Real-time SDA Module 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! +Sirdata provides a disruptive API that allows publishers and SDA compliant SSPs/DSPs to leverage its cutting-edge contextualization technology and its audience segments! -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. +User-based segments and page-level automatic contextual categories will be attached to bid request objects sent to different bidders 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 ! +Automatic or custom integration with Google Ad Manager and major bidders like Xandr's, Equativ's, Index Exchange's, Proxistore's, Magnite's or Triplelift's ! -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. +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. +Fully supports Seller Defined Audience ! Please find the full SDA taxonomy ids list here. -### Publisher Usage +Please contact for more information. + +## Publisher Usage + +### Configure Prebid.js Compile the Sirdata RTD module into your Prebid build: @@ -32,15 +30,39 @@ Compile the Sirdata RTD module into your Prebid build: 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. +`actualUrl` MUST be set with actual location of parent page if prebid.js is loaded in an iframe (e.g. hosted). It can be left blank ('') or removed otherwise. + +`partnerId` and `key` should be provided by your partnering SSP or get one and your dedicated taxonomy from Sirdata (). 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 any SSP or a partner to curate your media and operate cross-publishers campaigns with our data, please ask Sirdata () to whitelist him it in your account. + +#### Typical configuration +```javascript +pbjs.setConfig({ + // ... + realTimeData: { + auctionDelay: 1000, + dataProviders: [ + { + name: "SirdataRTDModule", + waitForIt: true, + params: { + partnerId: 1, + key: 1, + } + } + ] + } + // ... +}); ``` -pbjs.setConfig( - ... + +#### Advanced configuration + +```javascript +pbjs.setConfig({ + // ... realTimeData: { auctionDelay: 1000, dataProviders: [ @@ -48,85 +70,114 @@ pbjs.setConfig( name: "SirdataRTDModule", waitForIt: true, params: { - partnerId: 1, + 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) - }] + setGptKeyValues: true, + contextualMinRelevancyScore: 50, //Min score to filter contextual category globally (0-100 scale) + actualUrl: '' //top location url, for contextual categories } } ] } - ... -} + // ... +}); ``` ### 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 | +| Name | Type | Description | Notes | +|:-----------------------------------|:--------------------------|:----------------------------------------------------------------------------------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------| +| name | String | Real time data module name | Mandatory. Always 'SirdataRTDModule' | +| waitForIt | Boolean | Required to ensure that the auction is delayed until prefetch is complete | Optional. Default to false but recommended to true | +| params | Object | Settings | Optional | +| params.partnerId | Integer | Partner ID, required to get results and provided by Sirdata. Use 1 for tests and request your Id at | Mandatory. Default 1 | +| params.key | Integer | Key linked to Partner ID, required to get results and provided by Sirdata. Use 1 for tests and request your key at | Mandatory. Default 1 | +| params.setGptKeyValues | Boolean | Sets Targeting for GPT/GAM | Optional. Default to true | +| params.authorizedEids | Array of String | List of authorised Eids for graph. Set [] to prevent all Eids usage | Optional. Default to : ID5, pubProvidedId and sharedId | +| params.avoidPostContent | Boolean | Block contextual data POST from user's device (a crawler is use instead) | Optional. Default to false, and setting it to true results in your content downloaded by Sirdata crawler | +| params.contextualMinRelevancyScore | Integer | Min relevancy score to filter categories sent to the bidders (0-100 scale). | Optional. Defaults to 30. | +| params.bidders | Array of Object | Bidders you want to supply your own data to (works only with your private data bought to Sirdata) | Optional | 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 | +| 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 | Optional. Default curation ids are specified for main bidders | ### 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. +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 : + +```json { - "segments":[111111,222222], - "contextual_categories":{"333333":100}, - "shared_taxonomy":{ - "27446":{ //CurationId - "segments":[444444,555555], - "contextual_categories":{"666666":100} - } - } + "segments": [ + 111111, + 222222 + ], + "segtaxid": null, + "cattaxid": null, + "contextual_categories": { + "333333": 100 + }, + "shared_taxonomy": { + "27440": { + "segments": [ + 444444, + 555555 + ], + "segtaxid": 552, + "cattaxid": 553, + "contextual_categories": { + "666666": 100 + } + } + }, + "global_taxonomy": { + "9998": { + "segments": [ + 123, + 234 + ], + "segtaxid": 4, + "cattaxid": 7, + "contextual_categories": { + "345": 100, + "456": 100 + } + }, + "9999": { + "segments": [ + 12345, + 23456 + ], + "segtaxid": 550, + "cattaxid": 551, + "contextual_categories": { + "34567": 100, + "45678": 100 + } + } + } } - ``` + +```javascript function overrideAppnexus (adUnit, segmentsArray, dataObject, bid) { - for (var i = 0; i < segmentsArray.length; i++) { + for (var i = 0; i < segmentsArray.length; i++) { if (segmentsArray[i]) { bid.params.user.segments.push(segmentsArray[i]); } } } -pbjs.setConfig( - ... +pbjs.setConfig({ + // ... realTimeData: { auctionDelay: 1000, dataProviders: [ @@ -134,18 +185,17 @@ pbjs.setConfig( name: "SirdataRTDModule", waitForIt: true, params: { - partnerId: 1, + 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 + 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); }, @@ -153,17 +203,19 @@ pbjs.setConfig( } } ] - } + }, ... -} +}); ``` ### Testing To view an example of available segments returned by Sirdata's backends: -`gulp serve --modules=rtdModule,sirdataRtdProvider,appnexusBidAdapter` +```bash +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 +[http://localhost:9999/integrationExamples/gpt/sirdataRtdProvider_example.html] diff --git a/modules/sizeMapping.js b/modules/sizeMapping.js index fcd0b0963f2..9b2a37d0235 100644 --- a/modules/sizeMapping.js +++ b/modules/sizeMapping.js @@ -4,7 +4,6 @@ import {includes} from '../src/polyfill.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {setupAdUnitMediaTypes} from '../src/adapterManager.js'; -let installed = false; let sizeConfig = []; /** @@ -24,18 +23,16 @@ let sizeConfig = []; */ export function setSizeConfig(config) { sizeConfig = config; - if (!installed) { - setupAdUnitMediaTypes.before((next, adUnit, labels) => next(processAdUnitsForLabels(adUnit, labels), labels)); - installed = true; - } } + +setupAdUnitMediaTypes.before((next, adUnit, labels) => next(processAdUnitsForLabels(adUnit, labels), labels)); config.getConfig('sizeConfig', config => setSizeConfig(config.sizeConfig)); /** * Returns object describing the status of labels on the adUnit or bidder along with labels passed into requestBids * @param bidOrAdUnit the bidder or adUnit to get label info on * @param activeLabels the labels passed to requestBids - * @returns {LabelDescriptor} + * @returns {object} */ export function getLabels(bidOrAdUnit, activeLabels) { if (bidOrAdUnit.labelAll) { @@ -66,14 +63,18 @@ if (FEATURES.VIDEO) { } /** - * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match - * @param {Array} labels Labels specified on adUnit or bidder - * @param {boolean} labelAll if true, all labels must match to be enabled - * @param {Array} activeLabels Labels passed in through requestBids - * @param {object} mediaTypes A mediaTypes object describing the various media types (banner, video, native) - * @param {Array>} sizes Sizes specified on adUnit (deprecated) - * @param {Array} configs - * @returns {{labels: Array, sizes: Array>}} + * Resolves the unique set of the union of all sizes and labels that are active from a SizeConfig.mediaQuery match. + * + * @param {Object} options - The options object. + * @param {Array} [options.labels=[]] - Labels specified on adUnit or bidder. + * @param {boolean} [options.labelAll=false] - If true, all labels must match to be enabled. + * @param {Array} [options.activeLabels=[]] - Labels passed in through requestBids. + * @param {Object} mediaTypes - A mediaTypes object describing the various media types (banner, video, native). + * @param {Array} configs - An array of SizeConfig objects. + * @returns {Object} - An object containing the active status, media types, and filter results. + * @returns {boolean} return.active - Whether the media types are active. + * @returns {Object} return.mediaTypes - The media types object. + * @returns {Object} [return.filterResults] - The filter results before and after applying size filtering. */ export function resolveStatus({labels = [], labelAll = false, activeLabels = []} = {}, mediaTypes, configs = sizeConfig) { let maps = evaluateSizeConfig(configs); diff --git a/modules/smaatoBidAdapter.js b/modules/smaatoBidAdapter.js index 8f325aa13c9..f8a363cb084 100644 --- a/modules/smaatoBidAdapter.js +++ b/modules/smaatoBidAdapter.js @@ -19,10 +19,11 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js'; const BIDDER_CODE = 'smaato'; const SMAATO_ENDPOINT = 'https://prebid.ad.smaato.net/oapi/prebid'; -const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.0' +const SMAATO_CLIENT = 'prebid_js_$prebid.version$_3.2' const TTL = 300; const CURRENCY = 'USD'; const SUPPORTED_MEDIA_TYPES = [BANNER, VIDEO, NATIVE]; +const SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' export const spec = { code: BIDDER_CODE, @@ -141,7 +142,8 @@ export const spec = { meta: { advertiserDomains: bid.adomain, networkName: bid.bidderName, - agencyId: seatbid.seat + agencyId: seatbid.seat, + ...(bid.ext?.dsa && {dsa: bid.ext.dsa}) } }; @@ -195,6 +197,22 @@ export const spec = { * @return {UserSync[]} The user syncs which should be dropped. */ getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + if (syncOptions && syncOptions.pixelEnabled) { + let gdprParams = ''; + if (gdprConsent && gdprConsent.consentString) { + if (typeof gdprConsent.gdprApplies === 'boolean') { + gdprParams = `&gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; + } else { + gdprParams = `&gdpr_consent=${gdprConsent.consentString}`; + } + } + + return [{ + type: 'image', + url: SYNC_URL + gdprParams + }]; + } + return []; } } @@ -211,15 +229,19 @@ const converter = ortbConverter({ return bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies; } + function setPublisherId(node) { + deepSetValue(node, 'publisher.id', bidRequest.params.publisherId); + } + const request = buildRequest(imps, bidderRequest, context); const bidRequest = context.bidRequests[0]; - let siteContent; + let content; const mediaType = context.mediaType; if (mediaType === VIDEO) { const videoParams = bidRequest.mediaTypes[VIDEO]; if (videoParams.context === ADPOD) { request.imp = createAdPodImp(request.imp[0], videoParams); - siteContent = addOptionalAdpodParameters(videoParams); + content = addOptionalAdpodParameters(videoParams); } } @@ -241,19 +263,26 @@ const converter = ortbConverter({ if (request.site) { request.site.id = window.location.hostname - if (siteContent) { - request.site.content = siteContent; + if (content) { + request.site.content = content; + } + setPublisherId(request.site); + } else if (request.dooh) { + request.dooh.id = window.location.hostname + if (content) { + request.dooh.content = content; } + setPublisherId(request.dooh); } else { request.site = { id: window.location.hostname, domain: bidderRequest.refererInfo.domain || window.location.hostname, page: bidderRequest.refererInfo.page || window.location.href, ref: bidderRequest.refererInfo.ref, - content: siteContent || null + content: content || null } + setPublisherId(request.site); } - deepSetValue(request.site, 'publisher.id', bidRequest.params.publisherId); if (request.regs) { if (isGdprApplicable()) { @@ -276,18 +305,7 @@ const converter = ortbConverter({ } } - if (request.device) { - if (bidRequest.params.app) { - if (!deepAccess(request.device, 'geo')) { - const geo = deepAccess(bidRequest, 'params.app.geo'); - deepSetValue(request.device, 'geo', geo); - } - if (!deepAccess(request.device, 'ifa')) { - const ifa = deepAccess(bidRequest, 'params.app.ifa'); - deepSetValue(request.device, 'ifa', ifa); - } - } - } else { + if (!request.device) { request.device = { language: (navigator && navigator.language) ? navigator.language.split('-')[0] : '', ua: navigator.userAgent, @@ -295,6 +313,8 @@ const converter = ortbConverter({ h: screen.height, w: screen.width } + } + if (bidRequest.params.app) { if (!deepAccess(request.device, 'geo')) { const geo = deepAccess(bidRequest, 'params.app.geo'); deepSetValue(request.device, 'geo', geo); diff --git a/modules/smartadserverBidAdapter.js b/modules/smartadserverBidAdapter.js index 7edaaa36957..e0ddf0a66e6 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,17 +1,26 @@ -import { deepAccess, deepClone, isArrayOfNums, isFn, isInteger, isPlainObject, logError } from '../src/utils.js'; +import { + deepAccess, + deepClone, + isArray, + isArrayOfNums, + isEmpty, + isInteger, + logError +} from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; +import { getBidFloor } from '../libraries/equativUtils/equativUtils.js' import { registerBidder } from '../src/adapters/bidderFactory.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest + * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ const BIDDER_CODE = 'smartadserver'; const GVL_ID = 45; -const DEFAULT_FLOOR = 0.0; export const spec = { code: BIDDER_CODE, @@ -156,6 +165,9 @@ export const spec = { method: 'POST', url: (domain !== undefined ? domain : 'https://prg.smartadserver.com') + '/prebid/v1', data: JSON.stringify(payload), + options: { + browsingTopics: false + } }; }, @@ -163,7 +175,7 @@ export const spec = { * Makes server requests from the list of BidRequests. * * @param {BidRequest[]} validBidRequests an array of bids - * @param {BidderRequest} bidderRequest bidder request object + * @param {BidRequest} bidderRequest bidder request object * @return {ServerRequest[]} Info describing the request to the server. */ buildRequests: function (validBidRequests, bidderRequest) { @@ -196,11 +208,16 @@ export const spec = { sdc: sellerDefinedContext }; - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); + const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { payload.gpid = gpid; } + const dsa = deepAccess(bid, 'ortb2.regs.ext.dsa'); + if (dsa) { + payload.dsa = dsa; + } + if (bidderRequest) { if (bidderRequest.gdprConsent) { payload.addtl_consent = bidderRequest.gdprConsent.addtlConsent; @@ -239,7 +256,7 @@ export const spec = { if (isSupportedVideoContext) { let videoPayload = deepClone(payload); spec.fillPayloadForVideoBidRequest(videoPayload, videoMediaType, bid.params.video); - videoPayload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, VIDEO); + videoPayload.bidfloor = bid.params.bidfloor || getBidFloor(bid, adServerCurrency, VIDEO); bidRequests.push(spec.createServerRequest(videoPayload, bid.params.domain)); } } else { @@ -247,7 +264,7 @@ export const spec = { spec.fillPayloadForVideoBidRequest(payload, videoMediaType, bid.params.video); } - payload.bidfloor = bid.params.bidfloor || spec.getBidFloor(bid, adServerCurrency, type); + payload.bidfloor = bid.params.bidfloor || getBidFloor(bid, adServerCurrency, type); bidRequests.push(spec.createServerRequest(payload, bid.params.domain)); } else { bidRequests.push({}); @@ -282,7 +299,10 @@ export const spec = { netRevenue: response.isNetCpm, ttl: response.ttl, dspPixels: response.dspPixels, - meta: { advertiserDomains: response.adomain ? response.adomain : [] } + meta: { + ...isArray(response.adomain) && !isEmpty(response.adomain) ? { advertiserDomains: response.adomain } : {}, + ...!isEmpty(response.dsa) ? { dsa: response.dsa } : {} + } }; if (bidRequest.mediaType === VIDEO) { @@ -303,34 +323,12 @@ export const spec = { return bidResponses; }, - /** - * Get floors from Prebid Price Floors module - * - * @param {object} bid Bid request object - * @param {string} currency Ad server currency - * @param {string} mediaType Bid media type - * @return {number} Floor price - */ - getBidFloor: function (bid, currency, mediaType) { - if (!isFn(bid.getFloor)) { - return DEFAULT_FLOOR; - } - - const floor = bid.getFloor({ - currency: currency || 'USD', - mediaType, - size: '*' - }); - - return isPlainObject(floor) && !isNaN(floor.floor) ? floor.floor : DEFAULT_FLOOR; - }, - /** * User syncs. * * @param {*} syncOptions Publisher prebid configuration. * @param {*} serverResponses A successful response from the server. - * @return {syncs[]} An array of syncs that should be executed. + * @return {UserSync[]} An array of syncs that should be executed. */ getUserSyncs: function (syncOptions, serverResponses) { const syncs = []; diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 2889bd5358b..c9cc737fac7 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -1,190 +1,84 @@ -import {deepAccess, isFn, logError, logMessage} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { + buildPlacementProcessingFunction, + buildRequestsBase, + interpretResponseBuilder, + isBidRequestValid +} from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'smarthub'; +const ALIASES = [ + {code: 'attekmi', skipPbsAliasing: true}, + {code: 'markapp', skipPbsAliasing: true}, + {code: 'jdpmedia', skipPbsAliasing: true}, + {code: 'tredio', skipPbsAliasing: true}, + {code: 'felixads', skipPbsAliasing: true}, + {code: 'vimayx', skipPbsAliasing: true}, +]; +const BASE_URLS = { + attekmi: 'https://prebid.attekmi.com/pbjs', + smarthub: 'https://prebid.attekmi.com/pbjs', + markapp: 'https://markapp-prebid.attekmi.com/pbjs', + jdpmedia: 'https://jdpmedia-prebid.attekmi.com/pbjs', + tredio: 'https://tredio-prebid.attekmi.com/pbjs', + felixads: 'https://felixads-prebid.attekmi.com/pbjs', + vimayx: 'https://vimayx-prebid.attekmi.com/pbjs', +}; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency || !bid.hasOwnProperty('netRevenue')) { - return false; - } - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.width && bid.height && (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 { partnerName, seat, token, iabCat, minBidfloor, pos } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - partnerName: partnerName.toLowerCase(), - seat, - token, - iabCat, - minBidfloor, - pos, - 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; +const _getUrl = (partnerName) => { + const aliases = ALIASES.map(el => el.code); + if (aliases.includes(partnerName)) { + return BASE_URLS[partnerName]; } - return placement; + return `${BASE_URLS[BIDDER_CODE]}?partnerName=${partnerName}`; } -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', +const getPartnerName = (bid) => String(bid.params?.partnerName || bid.bidder).toLowerCase(); + +const getPlacementReqData = buildPlacementProcessingFunction({ + addPlacementType() {}, + addCustomFieldsToPlacement(bid, bidderRequest, placement) { + const { seat, token, iabCat, minBidfloor, pos } = bid.params; + Object.assign(placement, { + partnerName: getPartnerName(bid), + seat, + token, + iabCat, + minBidfloor, + pos, }); - return bidFloor.floor; - } catch (e) { - logError(e); - return 0; } -} - -function buildRequestParams(bidderRequest = {}, placements = []) { - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - - let location = refferLocation || winLocation; - const language = (navigator && navigator.language) ? navigator.language.split('-')[0] : ''; - const host = location.host; - const page = location.pathname; - const secure = location.protocol === 'https:' ? 1 : 0; - return { - deviceWidth, - deviceHeight, - language, - secure, - host, - page, - placements, - coppa: config.getConfig('coppa') === true ? 1 : 0, - ccpa: bidderRequest.uspConsent || undefined, - gdpr: bidderRequest.gdprConsent || undefined, - tmax: bidderRequest.timeout - }; +}) + +const buildRequests = (validBidRequests = [], bidderRequest = {}) => { + const bidsByPartner = validBidRequests.reduce((bidsByPartner, bid) => { + const partner = getPartnerName(bid); + (bidsByPartner[partner] = bidsByPartner[partner] || []).push(bid); + return bidsByPartner; + }, {}); + return Object.entries(bidsByPartner).map(([partner, validBidRequests]) => { + return buildRequestsBase({ + adUrl: _getUrl(partner), + bidderRequest, + validBidRequests, + placementProcessingFunction: getPlacementReqData + }) + }) } export const spec = { code: BIDDER_CODE, + aliases: ALIASES, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && params.partnerName && params.seat && params.token); - - 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 = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - const tempObj = {}; - - const len = validBidRequests.length; - for (let i = 0; i < len; i++) { - const bid = validBidRequests[i]; - const data = getPlacementReqData(bid); - tempObj[data.partnerName] = tempObj[data.partnerName] || []; - tempObj[data.partnerName].push(data); + isBidRequestValid: isBidRequestValid(['seat', 'token'], 'every'), + buildRequests, + interpretResponse: interpretResponseBuilder({ + addtlBidValidation(bid) { + return bid.hasOwnProperty('netRevenue') } - - return Object.keys(tempObj).map(key => { - const request = buildRequestParams(bidderRequest, tempObj[key]); - return { - method: 'POST', - url: `https://${key}-prebid.smart-hub.io/pbjs`, - 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/smarthubBidAdapter.md b/modules/smarthubBidAdapter.md index c09855303e2..dbdae927097 100644 --- a/modules/smarthubBidAdapter.md +++ b/modules/smarthubBidAdapter.md @@ -1,16 +1,16 @@ # Overview ``` -Module Name: SmartHub Bidder Adapter -Module Type: SmartHub Bidder Adapter -Maintainer: support@smart-hub.io +Module Name: Attekmi Bidder Adapter +Module Type: Attekmi Bidder Adapter +Maintainer: prebid@attekmi.com ``` # Description -Connects to SmartHub exchange for bids. +Connects to Attekmi exchange for bids. -SmartHub bid adapter supports Banner, Video (instream and outstream) and Native. +Attekmi bid adapter supports Banner, Video (instream and outstream) and Native. # Test Parameters ``` diff --git a/modules/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 8394814365c..1442199cd6d 100644 --- a/modules/smartxBidAdapter.js +++ b/modules/smartxBidAdapter.js @@ -119,12 +119,6 @@ export const spec = { const pos = getBidIdParameter('pos', bid.params) || 1; const api = getBidIdParameter('api', bid.params) || [2]; const protocols = getBidIdParameter('protocols', bid.params) || [2, 3, 5, 6]; - var contextcustom = deepAccess(bid, 'mediaTypes.video.context'); - var placement = 1; - - if (contextcustom === 'outstream') { - placement = 3; - } let smartxReq = [{ id: bid.bidId, @@ -144,7 +138,6 @@ export const spec = { maxbitrate: maxbitrate, delivery: delivery, pos: pos, - placement: placement, api: api, ext: ext }, @@ -429,6 +422,12 @@ function createOutstreamConfig(bid) { var playerListener = function callback(event) { switch (event) { + case 'AdError': + try { + window.sc_smartIntxtError(); + } catch (f) {} + break; + case 'AdSlotStarted': try { window.sc_smartIntxtStart(); diff --git a/modules/smartyadsAnalyticsAdapter.js b/modules/smartyadsAnalyticsAdapter.js new file mode 100644 index 00000000000..7784e0bc831 --- /dev/null +++ b/modules/smartyadsAnalyticsAdapter.js @@ -0,0 +1,133 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { ajax } from '../src/ajax.js'; + +const { + AUCTION_INIT, + AUCTION_END, + BID_WON, + BID_TIMEOUT, + BIDDER_ERROR, + BID_REJECTED, + BID_REQUESTED, + AD_RENDER_FAILED, + AD_RENDER_SUCCEEDED, + AUCTION_TIMEOUT +} = EVENTS; + +const URL = 'https://ps.itdsmr.com'; +const ANALYTICS_TYPE = 'endpoint'; +const BIDDER_CODE = 'smartyads'; +const GVLID = 534; + +let smartyParams = {}; + +let smartyadsAdapter = Object.assign({}, + adapter({ + url: URL, + analyticsType: ANALYTICS_TYPE, + }), + { + track({ eventType, args }) { + switch (eventType) { + case AUCTION_INIT: + case AUCTION_TIMEOUT: + case AUCTION_END: + auctionHandler(eventType, args); + break; + case BID_REQUESTED: + if (args.bidderCode === BIDDER_CODE) { + for (const bid of args.bids) { + const bidParams = bid.params?.length ? bid.params[0] : bid.params; + smartyParams[bid.bidId] = bidParams; + } + }; + break; + case BID_WON: + case BID_TIMEOUT: + case BID_REJECTED: + bidHandler(eventType, args); + break; + case BIDDER_ERROR: + onBidderError(args); + break; + case AD_RENDER_FAILED: + case AD_RENDER_SUCCEEDED: + onAdRender(eventType, args); + break; + default: + break; + } + } + } +); + +const sendDataToServer = (data) => { + ajax(URL, () => {}, JSON.stringify(data)); +} + +const auctionHandler = (eventType, data) => { + const auctionData = { + auctionId: data.auctionId, + status: eventType, + timeout: data.timeout, + metrics: data.metrics, + bidderRequests: data.bidderRequests?.map(bidderRequest => { + delete bidderRequest.gdprConsent; + delete bidderRequest.refererInfo; + return bidderRequest; + }).filter(request => request.bidderCode === BIDDER_CODE), + } + + sendDataToServer({ eventType, auctionData }); +} + +const bidHandler = (eventType, bid) => { + let bids = bid.length ? bid : [ bid ]; + + for (const bidObj of bids) { + let bidToSend; + + if (bidObj.bidderCode != BIDDER_CODE) { + if (eventType === BID_WON) { + bidToSend = { + cpm: bidObj.cpm, + auctionId: bidObj.auctionId + }; + } else continue; + } + + bidToSend = bidObj; + + if (eventType === BID_REJECTED) { + bidToSend.params = smartyParams[bid.requestId]; + } + + sendDataToServer({ eventType, bid: bidToSend }); + } +} + +const onBidderError = (data) => { + sendDataToServer({ + eventType: BIDDER_ERROR, + error: data.error, + bidderRequests: data?.bidderRequests?.length + ? data.bidderRequests.filter(request => request.bidderCode === BIDDER_CODE) + : [ data.bidderRequest ] + }); +} + +const onAdRender = (eventType, data) => { + if (data?.bid?.bidderCode === BIDDER_CODE) { + sendDataToServer({ eventType, renderData: data }); + } +} + +adapterManager.registerAnalyticsAdapter({ + adapter: smartyadsAdapter, + code: BIDDER_CODE, + gvlid: GVLID +}) + +export default smartyadsAdapter; diff --git a/modules/smartyadsAnalyticsAdapter.md b/modules/smartyadsAnalyticsAdapter.md new file mode 100644 index 00000000000..e08b2a02cb7 --- /dev/null +++ b/modules/smartyadsAnalyticsAdapter.md @@ -0,0 +1,11 @@ +#### Description + +Module that enables SmartyAds analytics + +### Configuration + +```javascript + pbjs.enableAnalytics({ + provider: 'smartyads' + }); +``` \ No newline at end of file diff --git a/modules/smartyadsBidAdapter.js b/modules/smartyadsBidAdapter.js index 6920983e50d..de7c61b7163 100644 --- a/modules/smartyadsBidAdapter.js +++ b/modules/smartyadsBidAdapter.js @@ -1,64 +1,15 @@ -import { logMessage } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { ajax } from '../src/ajax.js'; +import { getAdUrlByRegion } from '../libraries/smartyadsUtils/getAdUrlByRegion.js'; +import { interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'smartyads'; const GVLID = 534; -const adUrls = { - US_EAST: 'https://n1.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', - EU: 'https://n2.smartyads.com/?c=o&m=prebid&secret_key=prebid_js', - SGP: 'https://n6.smartyads.com/?c=o&m=prebid&secret_key=prebid_js' -} const URL_SYNC = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || - !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) || Boolean(bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.title && bid.native.image && bid.native.impressionTrackers); - default: - return false; - } -} - -function getAdUrlByRegion(bid) { - let adUrl; - - if (bid.params.region && adUrls[bid.params.region]) { - adUrl = adUrls[bid.params.region]; - } else { - try { - const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone; - const region = timezone.split('/')[0]; - - switch (region) { - case 'Europe': - adUrl = adUrls['EU']; - break; - case 'Asia': - adUrl = adUrls['SGP']; - break; - default: adUrl = adUrls['US_EAST']; - } - } catch (err) { - adUrl = adUrls['US_EAST']; - } - } - - return adUrl; -} - export const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -74,33 +25,20 @@ export const spec = { let winTop = window; let location; - // TODO: this odd try-catch block was copied in several adapters; it doesn't seem to be correct for cross-origin - try { - location = new URL(bidderRequest.refererInfo.page) - winTop = window.top; - } catch (e) { - location = winTop.location; - logMessage(e); - }; - + location = bidderRequest?.refererInfo ?? null; 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, + 'host': location?.domain ?? '', + 'page': location?.page ?? '', 'coppa': config.getConfig('coppa') === true ? 1 : 0, 'placements': placements, 'eeid': validBidRequests[0]?.userIdAsEids, 'ifa': bidderRequest?.ortb2?.device?.ifa, }; - 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 } @@ -137,59 +75,8 @@ export const spec = { } }, - interpretResponse: (serverResponse) => { - let response = []; - serverResponse = serverResponse.body; - for (let i = 0; i < serverResponse.length; i++) { - let resItem = serverResponse[i]; - if (isBidResponseValid(resItem)) { - response.push(resItem); - } - } - return response; - }, - - getUserSyncs: (syncOptions, serverResponses = [], gdprConsent = {}, uspConsent = '', gppConsent = '') => { - let syncs = []; - let { gdprApplies, consentString = '' } = gdprConsent; - - if (syncOptions.iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=iframe&us_privacy=${uspConsent}&gpp=${gppConsent}` - }); - } else { - syncs.push({ - type: 'image', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&type=image&us_privacy=${uspConsent}&gpp=${gppConsent}` - }); - } - - return syncs - }, - - onBidWon: function(bid) { - if (bid.winUrl) { - ajax(bid.winUrl, () => {}, JSON.stringify(bid)); - } else { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&winTest=1', () => {}, JSON.stringify(bid)); - } - } - }, - - onTimeout: function(bid) { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&bidTimeout=1', () => {}, JSON.stringify(bid)); - } - }, - - onBidderError: function(bid) { - if (bid?.postData && bid?.postData[0] && bid?.postData[0].params && bid?.postData[0].params[0].host == 'prebid') { - ajax('https://et-nd43.itdsmr.com/?c=o&m=prebid&secret_key=prebid_js&bidderError=1', () => {}, JSON.stringify(bid)); - } - }, - + interpretResponse, + getUserSyncs: getUserSyncs(URL_SYNC), }; registerBidder(spec); diff --git a/modules/smartytechBidAdapter.js b/modules/smartytechBidAdapter.js index af1442bd301..c081b49c2e6 100644 --- a/modules/smartytechBidAdapter.js +++ b/modules/smartytechBidAdapter.js @@ -127,14 +127,19 @@ export const spec = { creativeId: response.creativeId, netRevenue: true, currency: response.currency, - mediaType: BANNER - } + mediaType: BANNER, + meta: {} + }; if (response.mediaType === VIDEO) { bidObject.vastXml = response.ad; bidObject.mediaType = VIDEO; } + if (response.meta) { + bidObject.meta = response.meta; + } + return bidObject; }, diff --git a/modules/smartytechBidAdapter.md b/modules/smartytechBidAdapter.md index 9df57ddbde7..53b246e4cab 100644 --- a/modules/smartytechBidAdapter.md +++ b/modules/smartytechBidAdapter.md @@ -3,7 +3,7 @@ ``` Module Name: SmartyTech Bid Adapter Module Type: Bidder Adapter -Maintainer: info@adpartner.pro +Maintainer: info@fusify.io ``` # Description diff --git a/modules/smilewantedBidAdapter.js b/modules/smilewantedBidAdapter.js index 7d4a4bca615..7f83dc025cb 100644 --- a/modules/smilewantedBidAdapter.js +++ b/modules/smilewantedBidAdapter.js @@ -4,6 +4,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, NATIVE, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM, OUTSTREAM} from '../src/video.js'; +import {serializeSupplyChain} from '../libraries/schainSerializer/schainSerializer.js' import {convertOrtbRequestToProprietaryNative, toOrtbNativeRequest, toLegacyResponse} from '../src/native.js'; const BIDDER_CODE = 'smilewanted'; @@ -82,7 +83,8 @@ export const spec = { or from mediaTypes.banner.pos */ positionType: bid.params.positionType || '', - prebidVersion: '$prebid.version$' + prebidVersion: '$prebid.version$', + schain: serializeSupplyChain(bid.schain, ['asi', 'sid', 'hp', 'rid', 'name', 'domain', 'ext']), }; const floor = getBidFloor(bid); @@ -154,16 +156,16 @@ export const spec = { if (response) { const dealId = response.dealId || ''; const bidResponse = { - requestId: bidRequestData.bidId, + ad: response.ad, cpm: response.cpm, - width: response.width, - height: response.height, creativeId: response.creativeId, - dealId: response.dealId, currency: response.currency, + dealId: response.dealId, + height: response.height, netRevenue: response.isNetCpm, + requestId: bidRequestData.bidId, ttl: response.ttl, - ad: response.ad, + width: response.width, }; if (response.formatTypeSw === 'video_instream' || response.formatTypeSw === 'video_outstream') { @@ -209,28 +211,30 @@ export const spec = { * @param {Object} uspConsent The USP consent parameters * @return {UserSync[]} The user syncs which should be dropped. */ - getUserSyncs: function(syncOptions, responses, gdprConsent, uspConsent) { - let params = ''; - - if (gdprConsent && typeof gdprConsent.consentString === 'string') { - // add 'gdpr' only if 'gdprApplies' is defined - if (typeof gdprConsent.gdprApplies === 'boolean') { - params += `?gdpr=${Number(gdprConsent.gdprApplies)}&gdpr_consent=${gdprConsent.consentString}`; - } else { - params += `?gdpr_consent=${gdprConsent.consentString}`; + getUserSyncs: function (syncOptions, responses, gdprConsent, uspConsent) { + const syncs = []; + + if (syncOptions.iframeEnabled) { + let params = []; + + if (gdprConsent && typeof gdprConsent.consentString === 'string') { + // add 'gdpr' only if 'gdprApplies' is defined + 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 += `${params ? '&' : '?'}us_privacy=${encodeURIComponent(uspConsent)}`; - } + if (uspConsent) { + params.push(`us_privacy=${encodeURIComponent(uspConsent)}`); + } - const syncs = [] + const paramsStr = params.length > 0 ? '?' + params.join('&') : ''; - if (syncOptions.iframeEnabled) { syncs.push({ type: 'iframe', - url: 'https://csync.smilewanted.com' + params + url: 'https://csync.smilewanted.com' + paramsStr }); } diff --git a/modules/smootBidAdapter.js b/modules/smootBidAdapter.js new file mode 100644 index 00000000000..cd55dc5f253 --- /dev/null +++ b/modules/smootBidAdapter.js @@ -0,0 +1,19 @@ +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; + +const BIDDER_CODE = 'smoot'; +const AD_URL = 'https://endpoint1.smoot.ai/pbjs'; +const SYNC_URL = 'https://usync.smxconv.com'; + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: [BANNER, VIDEO, NATIVE], + + isBidRequestValid: isBidRequestValid(['placementId']), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) +}; + +registerBidder(spec); diff --git a/modules/smootBidAdapter.md b/modules/smootBidAdapter.md new file mode 100644 index 00000000000..322584f19ea --- /dev/null +++ b/modules/smootBidAdapter.md @@ -0,0 +1,80 @@ +# Overview + +``` +Module Name: Smoot Bidder Adapter +Module Type: Smoot Bidder Adapter +Maintainer: it.ops@smoot.ai +``` + +# Description + +Connects to Smoot exchange for bids. +Smoot 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: 'smoot', + params: { + placementId: 'testBanner', + } + } + ] + }, + { + code: 'addunit2', + mediaTypes: { + video: { + playerSize: [ [640, 480] ], + context: 'instream', + minduration: 5, + maxduration: 60, + } + }, + bids: [ + { + bidder: 'smoot', + params: { + placementId: 'testVideo', + } + } + ] + }, + { + code: 'addunit3', + mediaTypes: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + }, + bids: [ + { + bidder: 'smoot', + params: { + placementId: 'testNative', + } + } + ] + } + ]; +``` diff --git a/modules/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 5a327b05cd0..13731e2b7e1 100644 --- a/modules/snigelBidAdapter.js +++ b/modules/snigelBidAdapter.js @@ -2,8 +2,7 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {deepAccess, isArray, isFn, isPlainObject, inIframe, getDNT, generateUUID} from '../src/utils.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; -import {getGlobal} from '../src/prebidGlobal.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {getStorageManager} from '../src/storageManager.js'; const BIDDER_CODE = 'snigel'; @@ -17,6 +16,7 @@ const SESSION_ID_KEY = '_sn_session_pba'; const getConfig = config.getConfig; const storageManager = getStorageManager({bidderCode: BIDDER_CODE}); const refreshes = {}; +const placementCounters = {}; const pageViewId = generateUUID(); const pageViewStart = new Date().getTime(); let auctionCounter = 0; @@ -46,7 +46,8 @@ export const spec = { gdprConsent: gdprApplies === true ? hasFullGdprConsent(deepAccess(bidderRequest, 'gdprConsent')) : false, cur: getCurrencies(), test: getTestFlag(), - version: getGlobal().version, + version: 'v' + '$prebid.version$', + adapterVersion: '2.0', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp'), gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || deepAccess(bidderRequest, 'ortb2.regs.gpp_sid'), @@ -72,6 +73,7 @@ export const spec = { gpid: deepAccess(r, 'ortb2Imp.ext.gpid'), pbadslot: deepAccess(r, 'ortb2Imp.ext.data.pbadslot') || deepAccess(r, 'ortb2Imp.ext.gpid'), name: r.params.placement, + counter: getPlacementCounter(r.params.placement), sizes: r.sizes, floor: getPriceFloor(r, BANNER, FLOOR_MATCH_ALL_SIZES), refresh: getRefreshInformation(r.adUnitCode), @@ -183,6 +185,17 @@ function getRefreshInformation(adUnitCode) { }; } +function getPlacementCounter(placement) { + const counter = placementCounters[placement]; + if (counter === undefined) { + placementCounters[placement] = 0; + return 0; + } + + placementCounters[placement]++; + return placementCounters[placement]; +} + function mapIdToRequestId(id, bidRequest) { return bidRequest.bidderRequest.bids.filter((bid) => bid.adUnitCode === id)[0].bidId; } diff --git a/modules/sonobiAnalyticsAdapter.js b/modules/sonobiAnalyticsAdapter.js deleted file mode 100644 index 8242df7e0c5..00000000000 --- a/modules/sonobiAnalyticsAdapter.js +++ /dev/null @@ -1,275 +0,0 @@ -import { deepClone, logInfo, logError } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import {ajaxBuilder} from '../src/ajax.js'; - -let ajax = ajaxBuilder(0); - -export const DEFAULT_EVENT_URL = 'apex.go.sonobi.com/keymaker'; -const analyticsType = 'endpoint'; -const QUEUE_TIMEOUT_DEFAULT = 200; -const { - AUCTION_INIT, - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BIDDER_DONE, - BID_WON, - BID_RESPONSE, - BID_TIMEOUT -} = EVENTS; - -let initOptions = {}; -let auctionCache = {}; -let auctionTtl = 60 * 60 * 1000; - -function deleteOldAuctions() { - for (let auctionId in auctionCache) { - let auction = auctionCache[auctionId]; - if (Date.now() - auction.start > auctionTtl) { - delete auctionCache[auctionId]; - } - } -} - -function buildAuctionEntity(args) { - return { - 'id': args.auctionId, - 'start': args.timestamp, - 'timeout': args.timeout, - 'adUnits': {}, - 'stats': {}, - 'queue': [], - 'qTimeout': false - }; -} -function buildAdUnit(data) { - return `/${initOptions.pubId}/${initOptions.siteId}/${data.adUnitCode.toLowerCase()}`; -} -function getLatency(data) { - if (!data.responseTimestamp) { - return -1; - } else { - return data.responseTimestamp - data.requestTimestamp; - } -} -function getBid(data) { - if (data.cpm) { - return Math.round(data.cpm * 100); - } else { - return 0; - } -} -function buildItem(data, response, phase = 1) { - let size = data.width ? {width: data.width, height: data.height} : {width: data.sizes[0][0], height: data.sizes[0][1]}; - return { - 'bidid': data.bidId || data.requestId, - 'p': phase, - 'buyerid': data.bidder.toLowerCase(), - 'bid': getBid(data), - 'adunit_code': buildAdUnit(data), - 's': `${size.width}x${size.height}`, - 'latency': getLatency(data), - 'response': response, - 'jsLatency': getLatency(data), - 'buyername': data.bidder.toLowerCase() - }; -} -function sendQueue(auctionId) { - let auction = auctionCache[auctionId]; - let data = auction.queue; - auction.queue = []; - auction.qTimeout = false; - sonobiAdapter.sendData(auction, data); -} -function addToAuctionQueue(auctionId, id) { - let auction = auctionCache[auctionId]; - auction.queue = auction.queue.filter((item) => { - if (item.bidid !== id) { return true; } - return auction.stats[id].data.p !== item.p; - }); - auction.queue.push(deepClone(auction.stats[id].data)); - if (!auction.qTimeout) { - auction.qTimeout = setTimeout(() => { - sendQueue(auctionId); - }, initOptions.delay) - } -} -function updateBidStats(auctionId, id, data) { - let auction = auctionCache[auctionId]; - auction.stats[id].data = {...auction.stats[id].data, ...data}; - addToAuctionQueue(auctionId, id); - _logInfo('Updated Bid Stats: ', auction.stats[id]); - return auction.stats[id]; -} - -function handleOtherEvents(eventType, args) { - _logInfo('Other Event: ' + eventType, args); -} - -function handlerAuctionInit(args) { - auctionCache[args.auctionId] = buildAuctionEntity(args); - deleteOldAuctions(); - _logInfo('Auction Init', args); -} -function handlerBidRequested(args) { - let auction = auctionCache[args.auctionId]; - let data = []; - let phase = 1; - let response = 1; - args.bids.forEach(function (bidRequest) { - auction = auctionCache[bidRequest.auctionId] - let built = buildItem(bidRequest, response, phase); - auction.stats[built.bidid] = {id: built.bidid, adUnitCode: bidRequest.adUnitCode, data: built}; - addToAuctionQueue(args.auctionId, built.bidid); - }) - - _logInfo('Bids Requested ', data); -} - -function handlerBidAdjustment(args) { - _logInfo('Bid Adjustment', args); -} -function handlerBidderDone(args) { - _logInfo('Bidder Done', args); -} - -function handlerAuctionEnd(args) { - let winners = {}; - args.bidsReceived.forEach((bid) => { - if (!winners[bid.adUnitCode]) { - winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; - } else if (winners[bid.adUnitCode].cpm < bid.cpm) { - winners[bid.adUnitCode] = {bidId: bid.requestId, cpm: bid.cpm}; - } - }) - args.adUnitCodes.forEach((adUnitCode) => { - if (winners[adUnitCode]) { - let bidId = winners[adUnitCode].bidId; - updateBidStats(args.auctionId, bidId, {response: 4}); - } - }) - _logInfo('Auction End', args); - _logInfo('Auction Cache', auctionCache[args.auctionId].stats); -} -function handlerBidWon(args) { - let {auctionId, requestId} = args; - let res = updateBidStats(auctionId, requestId, {p: 3, response: 6}); - _logInfo('Bid Won ', args); - _logInfo('Bid Update Result: ', res); -} -function handlerBidResponse(args) { - let {auctionId, requestId, cpm, size, timeToRespond} = args; - updateBidStats(auctionId, requestId, {bid: cpm, s: size, jsLatency: timeToRespond, latency: timeToRespond, p: 2, response: 9}); - - _logInfo('Bid Response ', args); -} -function handlerBidTimeout(args) { - let {auctionId, bidId} = args; - _logInfo('Bid Timeout ', args); - updateBidStats(auctionId, bidId, {p: 2, response: 0, latency: args.timeout, jsLatency: args.timeout}); -} -let sonobiAdapter = Object.assign(adapter({url: DEFAULT_EVENT_URL, analyticsType}), { - track({eventType, args}) { - switch (eventType) { - case AUCTION_INIT: - handlerAuctionInit(args); - break; - case BID_REQUESTED: - handlerBidRequested(args); - break; - case BID_ADJUSTMENT: - handlerBidAdjustment(args); - break; - case BIDDER_DONE: - handlerBidderDone(args); - break; - case AUCTION_END: - handlerAuctionEnd(args); - break; - case BID_WON: - handlerBidWon(args); - break; - case BID_RESPONSE: - handlerBidResponse(args); - break; - case BID_TIMEOUT: - handlerBidTimeout(args); - break; - default: - handleOtherEvents(eventType, args); - break; - } - }, - -}); - -sonobiAdapter.originEnableAnalytics = sonobiAdapter.enableAnalytics; - -sonobiAdapter.enableAnalytics = function (config) { - if (this.initConfig(config)) { - _logInfo('Analytics adapter enabled', initOptions); - sonobiAdapter.originEnableAnalytics(config); - } -}; - -sonobiAdapter.initConfig = function (config) { - let isCorrectConfig = true; - initOptions = {}; - initOptions.options = deepClone(config.options); - - initOptions.pubId = initOptions.options.pubId || null; - initOptions.siteId = initOptions.options.siteId || null; - initOptions.delay = initOptions.options.delay || QUEUE_TIMEOUT_DEFAULT; - if (!initOptions.pubId) { - _logError('"options.pubId" is empty'); - isCorrectConfig = false; - } - if (!initOptions.siteId) { - _logError('"options.siteId" is empty'); - isCorrectConfig = false; - } - - initOptions.server = DEFAULT_EVENT_URL; - initOptions.host = initOptions.options.host || window.location.hostname; - this.initOptions = initOptions; - return isCorrectConfig; -}; - -sonobiAdapter.getOptions = function () { - return initOptions; -}; - -sonobiAdapter.sendData = function (auction, data) { - let url = 'https://' + initOptions.server + '?pageviewid=' + auction.id + '&corscred=1&pubId=' + initOptions.pubId + '&siteId=' + initOptions.siteId; - ajax( - url, - function () { _logInfo('Auction [' + auction.id + '] sent ', data); }, - JSON.stringify(data), - { - method: 'POST', - // withCredentials: true, - contentType: 'text/plain' - } - ); -}; - -function _logInfo(message, meta) { - logInfo(buildLogMessage(message), meta); -} - -function _logError(message) { - logError(buildLogMessage(message)); -} - -function buildLogMessage(message) { - return 'Sonobi Prebid Analytics: ' + message; -} - -adapterManager.registerAnalyticsAdapter({ - adapter: sonobiAdapter, - code: 'sonobi' -}); - -export default sonobiAdapter; diff --git a/modules/sonobiBidAdapter.js b/modules/sonobiBidAdapter.js index 9dd45ea0f35..2a3791f46fb 100644 --- a/modules/sonobiBidAdapter.js +++ b/modules/sonobiBidAdapter.js @@ -419,8 +419,6 @@ export function _getPlatform(context = window) { * @return {object} firstPartyData - Data object containing first party information */ function loadOrCreateFirstPartyData() { - var localStorageEnabled; - var FIRST_PARTY_KEY = '_iiq_fdata'; var tryParse = function (data) { try { @@ -431,19 +429,14 @@ function loadOrCreateFirstPartyData() { }; var readData = function (key) { if (hasLocalStorage()) { + // TODO FIX RULES VIOLATION + // eslint-disable-next-line prebid/no-global return window.localStorage.getItem(key); } return null; }; + // TODO FIX RULES VIOLATION - USE STORAGE MANAGER var hasLocalStorage = function () { - if (typeof localStorageEnabled != 'undefined') { return localStorageEnabled; } else { - try { - localStorageEnabled = !!window.localStorage; - return localStorageEnabled; - } catch (e) { - localStorageEnabled = false; - } - } return false; }; var generateGUID = function () { @@ -457,6 +450,8 @@ function loadOrCreateFirstPartyData() { var storeData = function (key, value) { try { if (hasLocalStorage()) { + // TODO FIX RULES VIOLATION + // eslint-disable-next-line prebid/no-global window.localStorage.setItem(key, value); } } catch (error) { diff --git a/modules/sovrnAnalyticsAdapter.js b/modules/sovrnAnalyticsAdapter.js deleted file mode 100644 index a89b365e074..00000000000 --- a/modules/sovrnAnalyticsAdapter.js +++ /dev/null @@ -1,287 +0,0 @@ -import {logError, timestamp} from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import adaptermanager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; -import {ajaxBuilder} from '../src/ajax.js'; -import {config} from '../src/config.js'; -import {find, includes} from '../src/polyfill.js'; -import {getRefererInfo} from '../src/refererDetection.js'; - -const ajax = ajaxBuilder(0) - -const { - AUCTION_END, - BID_REQUESTED, - BID_ADJUSTMENT, - BID_RESPONSE, - BID_WON -} = EVENTS; - -let pbaUrl = 'https://pba.aws.lijit.com/analytics' -let currentAuctions = {}; -const analyticsType = 'endpoint' - -const rootURL = (() => { - const ref = getRefererInfo(); - // TODO: does the fallback make sense here? - return ref.page || ref.topmostLocation; -})(); - -let sovrnAnalyticsAdapter = Object.assign(adapter({url: pbaUrl, analyticsType}), { - track({ eventType, args }) { - try { - if (eventType === BID_WON) { - new BidWinner(this.sovrnId, args).send(); - return - } - if (args && args.auctionId && currentAuctions[args.auctionId] && currentAuctions[args.auctionId].status === 'complete') { - throw new Error('Event Received after Auction Close Auction Id ' + args.auctionId) - } - if (args && args.auctionId && currentAuctions[args.auctionId] === undefined) { - currentAuctions[args.auctionId] = new AuctionData(this.sovrnId, args.auctionId) - } - switch (eventType) { - case BID_REQUESTED: - currentAuctions[args.auctionId].bidRequested(args) - break - case BID_ADJUSTMENT: - currentAuctions[args.auctionId].originalBid(args) - break - case BID_RESPONSE: - currentAuctions[args.auctionId].adjustedBid(args) - break - case AUCTION_END: - currentAuctions[args.auctionId].send(); - break - } - } catch (e) { - new LogError(e, this.sovrnId, {eventType, args}).send() - } - }, -}) - -sovrnAnalyticsAdapter.getAuctions = function () { - return currentAuctions; -}; - -sovrnAnalyticsAdapter.originEnableAnalytics = sovrnAnalyticsAdapter.enableAnalytics; - -// override enableAnalytics so we can get access to the config passed in from the page -sovrnAnalyticsAdapter.enableAnalytics = function (config) { - let sovrnId = '' - if (config && config.options && (config.options.sovrnId || config.options.affiliateId)) { - sovrnId = config.options.sovrnId || config.options.affiliateId; - } else { - logError('Need Sovrn Id to log auction results. Please contact a Sovrn representative if you do not know your Sovrn Id.') - return - } - sovrnAnalyticsAdapter.sovrnId = sovrnId; - if (config.options.pbaUrl) { - pbaUrl = config.options.pbaUrl; - } - sovrnAnalyticsAdapter.originEnableAnalytics(config) // call the base class function -}; - -adaptermanager.registerAnalyticsAdapter({ - adapter: sovrnAnalyticsAdapter, - code: 'sovrn' -}); - -/** Class Representing a Winning Bid */ -class BidWinner { - /** - * Creates a new bid winner - * @param {string} sovrnId - the affiliate id from the analytics config - * @param {*} event - the args object from the auction event - */ - constructor(sovrnId, event) { - this.body = {} - // eslint-disable-next-line no-undef - this.body.prebidVersion = $$REPO_AND_VERSION$$ - this.body.sovrnId = sovrnId - this.body.winningBid = JSON.parse(JSON.stringify(event)) - this.body.url = rootURL - this.body.payload = 'winner' - delete this.body.winningBid.ad - } - - /** - * Sends the auction to the the ingest server - */ - send() { - this.body.ts = timestamp() - ajax( - pbaUrl, - null, - JSON.stringify(this.body), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} - -/** Class representing an Auction */ -class AuctionData { - /** - * Create a new auction data collector - * @param {string} sovrnId - the affiliate id from the analytics config - * @param {string} auctionId - the auction id from the auction event - */ - constructor(sovrnId, auctionId) { - this.auction = {} - // eslint-disable-next-line no-undef - this.auction.prebidVersion = $$REPO_AND_VERSION$$ - this.auction.sovrnId = sovrnId - this.auction.auctionId = auctionId - this.auction.payload = 'auction' - this.auction.timeouts = { - buffer: config.getConfig('timeoutBuffer'), - bidder: config.getConfig('bidderTimeout'), - } - this.auction.priceGranularity = config.getConfig('priceGranularity') - this.auction.url = rootURL - this.auction.requests = [] - this.auction.unsynced = [] - this.dropBidFields = ['auctionId', 'ad', 'requestId', 'bidderCode'] - - setTimeout(function(id) { - delete currentAuctions[id] - }, 300000, this.auction.auctionId) - } - - /** - * Record a bid request event - * @param {*} event - the args object from the auction event - */ - bidRequested(event) { - const eventCopy = JSON.parse(JSON.stringify(event)) - delete eventCopy.doneCbCallCount - delete eventCopy.auctionId - this.auction.requests.push(eventCopy) - } - - /** - * Finds the bid from the auction that the event is associated with - * @param {*} event - the args object from the auction event - * @return {*} - the bid - */ - findBid(event) { - const bidder = find(this.auction.requests, r => (r.bidderCode === event.bidderCode)) - if (!bidder) { - this.auction.unsynced.push(JSON.parse(JSON.stringify(event))) - } - let bid = find(bidder.bids, b => (b.bidId === event.requestId)) - - if (!bid) { - event.unmatched = true - bidder.bids.push(JSON.parse(JSON.stringify(event))) - } - return bid - } - - /** - * Records the original bid before any adjustments have been made - * @param {*} event - the args object from the auction event - * NOTE: the bid adjustment occurs before the bid response - * the bid adjustment seems to be the bid ready to be adjusted - */ - originalBid(event) { - let bid = this.findBid(event) - if (bid) { - Object.assign(bid, JSON.parse(JSON.stringify(event))) - this.dropBidFields.forEach((f) => delete bid[f]) - } - } - - /** - * Replaces original values with adjusted values and records the original values for changed values - * in bid.originalValues - * @param {*} event - the args object from the auction event - */ - adjustedBid(event) { - let bid = this.findBid(event) - if (bid) { - bid.originalValues = Object.keys(event).reduce((o, k) => { - if (JSON.stringify(bid[k]) !== JSON.stringify(event[k]) && !includes(this.dropBidFields, k)) { - o[k] = bid[k] - bid[k] = event[k] - } - return o - }, {}) - } - } - - /** - * Sends the auction to the the ingest server - */ - send() { - let maxBids = {} - this.auction.requests.forEach(request => { - request.bids.forEach(bid => { - maxBids[bid.adUnitCode] = maxBids[bid.adUnitCode] || {cpm: 0} - if (bid.cpm > maxBids[bid.adUnitCode].cpm) { - maxBids[bid.adUnitCode] = bid - } - }) - }) - Object.keys(maxBids).forEach(unit => { - maxBids[unit].isAuctionWinner = true - }) - this.auction.ts = timestamp() - ajax( - pbaUrl, - () => { - currentAuctions[this.auction.auctionId] = {status: 'complete', auctionId: this.auction.auctionId} - }, - JSON.stringify(this.auction), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} -class LogError { - constructor(e, sovrnId, data) { - this.error = {} - this.error.payload = 'error' - this.error.message = e.message - this.error.stack = e.stack - this.error.data = data - // eslint-disable-next-line no-undef - this.error.prebidVersion = $$REPO_AND_VERSION$$ - this.error.sovrnId = sovrnId - this.error.url = rootURL - this.error.userAgent = navigator.userAgent - } - send() { - if (this.error.data && this.error.data.requests) { - this.error.data.requests.forEach(request => { - if (request.bids) { - request.bids.forEach(bid => { - if (bid.ad) { - delete bid.ad - } - }) - } - }) - } - if (ErrorEvent.data && this.error.data.ad) { - delete this.error.data.ad - } - this.error.ts = timestamp() - ajax( - pbaUrl, - null, - JSON.stringify(this.error), - { - contentType: 'application/json', - method: 'POST', - } - ) - } -} - -export default sovrnAnalyticsAdapter; diff --git a/modules/sovrnAnalyticsAdapter.md b/modules/sovrnAnalyticsAdapter.md deleted file mode 100644 index b4fe7c971a2..00000000000 --- a/modules/sovrnAnalyticsAdapter.md +++ /dev/null @@ -1,23 +0,0 @@ -# Overview - -``` -Module Name: Sovrn Analytics Adapter -Module Type: Analytics Adapter -Maintainer: exchange@sovrn.com -``` - -# Description - -Sovrn's analytics adaptor allows you to view detailed auction information in Meridian. - -For more information, visit Sovrn.com. - -# Test Parameters -``` -{ - provider: 'sovrn', - options: { - sovrnId: 'xxxxx', // Sovrn ID (required) you can get this by contacting Sovrn support. - } -} -``` diff --git a/modules/sovrnBidAdapter.js b/modules/sovrnBidAdapter.js index b6563cac4c5..d4e53c00472 100644 --- a/modules/sovrnBidAdapter.js +++ b/modules/sovrnBidAdapter.js @@ -6,7 +6,10 @@ import { logError, deepAccess, isInteger, - logWarn, getBidIdParameter, isEmptyStr + logWarn, + getBidIdParameter, + isEmptyStr, + mergeDeep } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js' import { @@ -14,35 +17,18 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js' +import { COMMON_ORTB_VIDEO_PARAMS } from '../libraries/deepintentUtils/index.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ const ORTB_VIDEO_PARAMS = { - 'mimes': (value) => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string'), - 'minduration': (value) => isInteger(value), - 'maxduration': (value) => isInteger(value), - 'protocols': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 10), - 'w': (value) => isInteger(value), - 'h': (value) => isInteger(value), - 'startdelay': (value) => isInteger(value), + ...COMMON_ORTB_VIDEO_PARAMS, 'placement': (value) => isInteger(value) && value >= 1 && value <= 5, - 'linearity': (value) => [1, 2].indexOf(value) !== -1, - 'skip': (value) => [0, 1].indexOf(value) !== -1, - 'skipmin': (value) => isInteger(value), - 'skipafter': (value) => isInteger(value), - 'sequence': (value) => isInteger(value), - 'battr': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 17), - 'maxextended': (value) => isInteger(value), - 'minbitrate': (value) => isInteger(value), - 'maxbitrate': (value) => isInteger(value), - 'boxingallowed': (value) => [0, 1].indexOf(value) !== -1, - 'playbackmethod': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6), - 'playbackend': (value) => [1, 2, 3].indexOf(value) !== -1, + 'plcmt': (value) => isInteger(value) && value >= 1 && value <= 4, 'delivery': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 3), 'pos': (value) => isInteger(value) && value >= 1 && value <= 7, - 'api': (value) => Array.isArray(value) && value.every(v => v >= 1 && v <= 6) } const REQUIRED_VIDEO_PARAMS = { @@ -139,7 +125,7 @@ export const spec = { } const auctionEnvironment = bid?.ortb2Imp?.ext?.ae - if (bidderRequest.fledgeEnabled && isInteger(auctionEnvironment)) { + if (bidderRequest.paapi?.enabled && isInteger(auctionEnvironment)) { imp.ext = imp.ext || {} imp.ext.ae = auctionEnvironment } else { @@ -197,6 +183,12 @@ export const spec = { deepSetValue(sovrnBidReq, 'regs.gpp_sid', bidderRequest.gppConsent.applicableSections); } + // if present, merge device object from ortb2 into `sovrnBidReq.device` + if (bidderRequest?.ortb2?.device) { + sovrnBidReq.device = sovrnBidReq.device || {}; + mergeDeep(sovrnBidReq.device, bidderRequest.ortb2.device); + } + if (eids) { deepSetValue(sovrnBidReq, 'user.ext.eids', eids) if (criteoId) { @@ -288,7 +280,7 @@ export const spec = { }) return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids @@ -381,7 +373,7 @@ function _getBidFloors(bid) { mediaType: bid.mediaTypes && bid.mediaTypes.banner ? 'banner' : 'video', size: '*' }) : {} - const floorModuleValue = parseFloat(floorInfo.floor) + const floorModuleValue = parseFloat(floorInfo?.floor) if (!isNaN(floorModuleValue)) { return floorModuleValue } diff --git a/modules/sparteoBidAdapter.js b/modules/sparteoBidAdapter.js index 0bccc1ec140..2bb08707c85 100644 --- a/modules/sparteoBidAdapter.js +++ b/modules/sparteoBidAdapter.js @@ -24,12 +24,14 @@ const converter = ortbConverter({ request(buildRequest, imps, bidderRequest, context) { const request = buildRequest(imps, bidderRequest, context); + deepSetValue(request, 'site.publisher.ext.params.pbjsVersion', '$prebid.version$'); + if (bidderRequest.bids[0].params.networkId) { - deepSetValue(request, 'site.publisher.ext.params.networkId', bidderRequest.bids[0].params.networkId); + request.site.publisher.ext.params.networkId = bidderRequest.bids[0].params.networkId; } if (bidderRequest.bids[0].params.publisherId) { - deepSetValue(request, 'site.publisher.ext.params.publisherId', bidderRequest.bids[0].params.publisherId); + request.site.publisher.ext.params.publisherId = bidderRequest.bids[0].params.publisherId; } return request; @@ -38,6 +40,7 @@ const converter = ortbConverter({ const imp = buildImp(bidRequest, context); deepSetValue(imp, 'ext.sparteo.params', bidRequest.params); + imp.ext.sparteo.params.adUnitCode = bidRequest.adUnitCode; return imp; }, @@ -76,7 +79,8 @@ export const spec = { } if (!bid.params.networkId && !bid.params.publisherId) { - logError('The networkId or publisherId is required'); + // publisherId is deprecated but is still accepted for now for retrocompatibility purpose. + logError('The networkId is required'); return false; } diff --git a/modules/spotxBidAdapter.js b/modules/spotxBidAdapter.js deleted file mode 100644 index c1f1c5159fc..00000000000 --- a/modules/spotxBidAdapter.js +++ /dev/null @@ -1,528 +0,0 @@ -import { - logError, - deepAccess, - isArray, - getDNT, - deepSetValue, - isEmpty, - _each, - logMessage, - logWarn, - isBoolean, - isNumber, - isPlainObject, - isFn, - setScriptAttributes, - getBidIdParameter -} from '../src/utils.js'; -import { config } from '../src/config.js'; -import { Renderer } from '../src/Renderer.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import { VIDEO } from '../src/mediaTypes.js'; -import { loadExternalScript } from '../src/adloader.js'; - -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerRequest} ServerRequest - */ - -const BIDDER_CODE = 'spotx'; -const URL = 'https://search.spotxchange.com/openrtb/2.3/dados/'; -const ORTB_VERSION = '2.3'; -export const GOOGLE_CONSENT = { consented_providers: ['3', '7', '11', '12', '15', '20', '22', '35', '43', '46', '48', '55', '57', '61', '62', '66', '70', '80', '83', '85', '86', '89', '93', '108', '122', '124', '125', '126', '131', '134', '135', '136', '143', '144', '147', '149', '153', '154', '159', '161', '162', '165', '167', '171', '178', '184', '188', '192', '195', '196', '202', '209', '211', '218', '221', '228', '229', '230', '236', '239', '241', '253', '255', '259', '266', '271', '272', '274', '286', '291', '294', '303', '308', '310', '311', '313', '314', '316', '317', '322', '323', '327', '336', '338', '340', '348', '350', '358', '359', '363', '367', '370', '371', '384', '385', '389', '393', '394', '397', '398', '407', '414', '415', '424', '429', '430', '432', '436', '438', '440', '442', '443', '445', '448', '449', '453', '459', '479', '482', '486', '491', '492', '494', '495', '503', '505', '510', '522', '523', '528', '537', '540', '550', '559', '560', '568', '571', '574', '575', '576', '584', '585', '587', '588', '590', '591', '592', '595', '609', '621', '624', '723', '725', '733', '737', '776', '780', '782', '787', '797', '798', '802', '803', '814', '817', '820', '821', '827', '829', '839', '853', '864', '867', '874', '899', '904', '922', '926', '931', '932', '933', '938', '955', '973', '976', '979', '981', '985', '987', '991', '1003', '1024', '1025', '1027', '1028', '1029', '1033', '1034', '1040', '1047', '1048', '1051', '1052', '1053', '1054', '1062', '1063', '1067', '1072', '1085', '1092', '1095', '1097', '1099', '1100', '1107', '1126', '1127', '1143', '1149', '1152', '1162', '1166', '1167', '1170', '1171', '1172', '1188', '1192', '1199', '1201', '1204', '1205', '1211', '1212', '1215', '1220', '1225', '1226', '1227', '1230', '1232', '1236', '1241', '1248', '1250', '1252', '1268', '1275', '1276', '1284', '1286', '1298', '1301', '1307', '1312', '1313', '1317', '1329', '1336', '1344', '1345', '1356', '1362', '1365', '1375', '1403', '1409', '1411', '1415', '1416', '1419', '1423', '1440', '1442', '1449', '1451', '1455', '1456', '1468', '1496', '1503', '1509', '1512', '1514', '1517', '1520', '1525', '1540', '1547', '1548', '1555', '1558', '1570', '1575', '1577', '1579', '1583', '1584', '1591', '1598', '1603', '1608', '1613', '1616', '1626', '1631', '1633', '1638', '1642', '1648', '1651', '1652', '1653', '1660', '1665', '1667', '1669', '1671', '1674', '1677', '1678', '1682', '1684', '1697', '1703', '1705', '1716', '1720', '1721', '1722', '1725', '1732', '1733', '1735', '1739', '1741', '1745', '1750', '1753', '1760', '1765', '1769', '1776', '1780', '1782', '1786', '1791', '1794', '1799', '1800', '1801', '1810', '1827', '1831', '1832', '1834', '1837', '1840', '1843', '1844', '1845', '1858', '1859', '1863', '1866', '1870', '1872', '1875', '1878', '1880', '1882', '1883', '1889', '1892', '1896', '1898', '1899', '1902', '1905', '1911', '1922', '1928', '1929', '1934', '1942', '1943', '1944', '1945', '1958', '1960', '1962', '1963', '1964', '1967', '1968', '1978', '1985', '1986', '1987', '1998', '2003', '2007', '2012', '2013', '2027', '2035', '2038', '2039', '2044', '2047', '2052', '2056', '2059', '2062', '2064', '2068', '2070', '2072', '2078', '2079', '2084', '2088', '2090', '2095', '2100', '2103', '2107', '2109', '2113', '2115', '2121', '2127', '2130', '2133', '2137', '2140', '2141', '2145', '2147', '2150', '2156', '2166', '2170', '2171', '2176', '2177', '2179', '2183', '2186', '2192', '2198', '2202', '2205', '2214', '2216', '2219', '2220', '2222', '2223', '2224', '2225', '2227', '2228', '2234', '2238', '2247', '2251', '2253', '2262', '2264', '2271', '2276', '2278', '2279', '2282', '2290', '2292', '2295', '2299', '2305', '2306', '2310', '2311', '2312', '2315', '2320', '2325', '2328', '2331', '2334', '2335', '2336', '2337', '2343', '2346', '2354', '2357', '2358', '2359', '2366', '2370', '2373', '2376', '2377', '2380', '2382', '2387', '2389', '2392', '2394', '2400', '2403', '2405', '2406', '2407', '2410', '2411', '2413', '2414', '2415', '2416', '2418', '2422', '2425', '2427', '2435', '2437', '2440', '2441', '2447', '2453', '2459', '2461', '2462', '2464', '2467', '2468', '2472', '2477', '2481', '2484', '2486', '2492', '2493', '2496', '2497', '2498', '2499', '2504', '2506', '2510', '2511', '2512', '2517', '2526', '2527', '2531', '2532', '2534', '2542', '2544', '2552', '2555', '2559', '2563', '2564', '2567', '2568', '2569', '2571', '2572', '2573', '2575', '2577', '2579', '2583', '2584', '2586', '2589', '2595', '2596', '2597', '2601', '2604', '2605', '2609', '2610', '2612', '2614', '2621', '2622', '2624', '2628', '2629', '2632', '2634', '2636', '2639', '2643', '2645', '2646', '2647', '2649', '2650', '2651', '2652', '2656', '2657', '2658', '2660', '2661', '2662', '2663', '2664', '2669', '2670', '2673', '2676', '2677', '2678', '2681', '2682', '2684', '2685', '2686', '2689', '2690', '2691', '2695', '2698', '2699', '2702', '2704', '2705', '2706', '2707', '2709', '2710', '2713', '2714', '2727', '2729', '2739', '2758', '2765', '2766', '2767', '2768', '2770', '2771', '2772', '2776', '2777', '2778', '2779', '2780', '2783', '2784', '2786', '2787', '2791', '2792', '2793', '2797', '2798', '2801', '2802', '2803', '2805', '2808', '2809', '2810', '2811', '2812', '2813', '2814', '2817', '2818', '2824', '2826', '2827', '2829', '2830', '2831', '2832', '2834', '2836', '2838', '2840', '2842', '2843', '2844', '2850', '2851', '2852', '2854', '2858', '2860', '2862', '2864', '2865', '2866', '2867', '2868', '2869', '2871'] }; - -export const spec = { - code: BIDDER_CODE, - gvlid: 165, - supportedMediaTypes: [VIDEO], - - /** - * Determines whether or not the given bid request is valid. - * From Prebid.js: isBidRequestValid - Verify the the AdUnits.bids, respond with true (valid) or false (invalid). - * - * @param {object} bid The bid to validate. - * @return {boolean} True if this is a valid bid, and false otherwise. - */ - isBidRequestValid: function(bid) { - if (bid && typeof bid.params !== 'object') { - logError(BIDDER_CODE + ': params is not defined or is incorrect in the bidder settings.'); - return false; - } - - if (!deepAccess(bid, 'mediaTypes.video')) { - logError(BIDDER_CODE + ': mediaTypes.video is not present in the bidder settings.'); - return false; - } - - const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - if (!playerSize || !isArray(playerSize)) { - logError(BIDDER_CODE + ': mediaTypes.video.playerSize is not defined in the bidder settings.'); - return false; - } - - if (!getBidIdParameter('channel_id', bid.params)) { - logError(BIDDER_CODE + ': channel_id is not present in bidder params'); - return false; - } - - if (deepAccess(bid, 'mediaTypes.video.context') == 'outstream' || deepAccess(bid, 'params.ad_unit') == 'outstream') { - if (!getBidIdParameter('outstream_function', bid.params)) { - if (!getBidIdParameter('outstream_options', bid.params)) { - logError(BIDDER_CODE + ': please define outstream_options parameter or override the default SpotX outstream rendering by defining your own Outstream function using field outstream_function.'); - return false; - } - if (!getBidIdParameter('slot', bid.params.outstream_options)) { - logError(BIDDER_CODE + ': please define parameter slot in outstream_options object in the configuration.'); - return false; - } - } - } - - return true; - }, - - /** - * Make a server request from the list of BidRequests. - * from Prebid.js: buildRequests - Takes an array of valid bid requests, all of which are guaranteed to have passed the isBidRequestValid() test. - * - * @param {BidRequest[]} bidRequests A non-empty list of bid requests which should be sent to the Server. - * @param {object} bidderRequest - The master bidRequest object. - * @return {ServerRequest} Info describing the request to the server. - */ - buildRequests: function(bidRequests, bidderRequest) { - // TODO: does the fallback make sense here? - const referer = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const isPageSecure = !!referer.match(/^https:/); - - const siteId = ''; - const spotxRequests = bidRequests.map(function(bid) { - let page; - if (getBidIdParameter('page', bid.params)) { - page = getBidIdParameter('page', bid.params); - } else { - page = referer; - } - - const channelId = getBidIdParameter('channel_id', bid.params); - let pubcid = null; - - const playerSize = deepAccess(bid, 'mediaTypes.video.playerSize'); - const contentWidth = playerSize[0][0]; - const contentHeight = playerSize[0][1]; - - const secure = isPageSecure || (getBidIdParameter('secure', bid.params) ? 1 : 0); - - const ext = { - sdk_name: 'Prebid 1+', - versionOrtb: ORTB_VERSION - }; - - if (getBidIdParameter('hide_skin', bid.params) != '') { - ext.hide_skin = +!!getBidIdParameter('hide_skin', bid.params); - } - - if (getBidIdParameter('ad_volume', bid.params) != '') { - ext.ad_volume = getBidIdParameter('ad_volume', bid.params); - } - - if (getBidIdParameter('ad_unit', bid.params) != '') { - ext.ad_unit = getBidIdParameter('ad_unit', bid.params); - } - - if (getBidIdParameter('outstream_options', bid.params) != '') { - ext.outstream_options = getBidIdParameter('outstream_options', bid.params); - } - - if (getBidIdParameter('outstream_function', bid.params) != '') { - ext.outstream_function = getBidIdParameter('outstream_function', bid.params); - } - - if (getBidIdParameter('custom', bid.params) != '') { - ext.custom = getBidIdParameter('custom', bid.params); - } - - if (getBidIdParameter('pre_market_bids', bid.params) != '' && isArray(getBidIdParameter('pre_market_bids', bid.params))) { - const preMarketBids = getBidIdParameter('pre_market_bids', bid.params); - ext.pre_market_bids = []; - for (let i in preMarketBids) { - const preMarketBid = preMarketBids[i]; - let vastStr = ''; - if (preMarketBid['vast_url']) { - vastStr = '' + preMarketBid['vast_url'] + ''; - } else if (preMarketBid['vast_string']) { - vastStr = preMarketBid['vast_string']; - } - ext.pre_market_bids.push({ - id: preMarketBid['deal_id'], - seatbid: [{ - bid: [{ - impid: Date.now(), - dealid: preMarketBid['deal_id'], - price: preMarketBid['price'], - adm: vastStr - }] - }], - cur: preMarketBid['currency'], - ext: { - event_log: [{}] - } - }); - } - } - - const mimes = getBidIdParameter('mimes', bid.params) || deepAccess(bid, 'mediaTypes.video.mimes') || ['application/javascript', 'video/mp4', 'video/webm']; - - const spotxReq = { - id: bid.bidId, - secure: secure, - video: { - w: contentWidth, - h: contentHeight, - ext: ext, - mimes: mimes - } - }; - - if (isFn(bid.getFloor)) { - let floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: 'video', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - spotxReq.bidfloor = floorInfo.floor; - } - } else if (getBidIdParameter('price_floor', bid.params) != '') { - spotxReq.bidfloor = getBidIdParameter('price_floor', bid.params); - } - - const startdelay = getBidIdParameter('start_delay', bid.params) || deepAccess(bid, 'mediaTypes.video.startdelay'); - if (startdelay) { - spotxReq.video.startdelay = 0 + Boolean(startdelay); - } - - const minduration = getBidIdParameter('min_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.minduration'); - if (minduration) { - spotxReq.video.minduration = minduration; - } - - const maxduration = getBidIdParameter('max_duration', bid.params) || deepAccess(bid, 'mediaTypes.video.maxduration'); - if (maxduration) { - spotxReq.video.maxduration = maxduration; - } - - const placement = getBidIdParameter('placement_type', bid.params) || deepAccess(bid, 'mediaTypes.video.placement'); - if (placement) { - spotxReq.video.ext.placement = placement; - } - - const position = getBidIdParameter('position', bid.params) || deepAccess(bid, 'mediaTypes.video.pos'); - if (position) { - spotxReq.video.ext.pos = position; - } - - if (bid.crumbs && bid.crumbs.pubcid) { - pubcid = bid.crumbs.pubcid; - } - - const language = navigator.language ? 'language' : 'userLanguage'; - const device = { - h: screen.height, - w: screen.width, - dnt: getDNT() ? 1 : 0, - language: navigator[language].split('-')[0], - make: navigator.vendor ? navigator.vendor : '', - ua: navigator.userAgent - }; - - const requestPayload = { - id: channelId, - imp: spotxReq, - site: { - id: siteId, - page: page, - content: 'content', - }, - device: device, - ext: { - wrap_response: 1 - } - }; - - // If the publisher asks to ignore the bidder cache key we need to return the full vast xml - // so that it can be cached on the publishes specified server. - if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { - requestPayload['ext']['wrap_response'] = 0; - } - - if (getBidIdParameter('number_of_ads', bid.params)) { - requestPayload['ext']['number_of_ads'] = getBidIdParameter('number_of_ads', bid.params); - } - - const userExt = {}; - - if (getBidIdParameter('spotx_all_google_consent', bid.params) == 1) { - userExt['consented_providers_settings'] = GOOGLE_CONSENT; - } - - // Add GDPR flag and consent string - if (bidderRequest && bidderRequest.gdprConsent) { - userExt.consent = bidderRequest.gdprConsent.consentString; - - if (typeof bidderRequest.gdprConsent.gdprApplies !== 'undefined') { - deepSetValue(requestPayload, 'regs.ext.gdpr', (bidderRequest.gdprConsent.gdprApplies ? 1 : 0)); - } - } - - if (bidderRequest && bidderRequest.uspConsent) { - deepSetValue(requestPayload, 'regs.ext.us_privacy', bidderRequest.uspConsent); - } - - if (bid.userIdAsEids) { - userExt.eids = bid.userIdAsEids; - - userExt.eids.forEach(eid => { - if (eid.source === 'uidapi.com') { - eid.uids.forEach(uid => { - uid.ext = uid.ext || {}; - uid.ext.rtiPartner = 'UID2' - }); - } - }); - } - - // Add common id if available - if (pubcid) { - userExt.fpc = pubcid; - } - - // Add schain object if it is present - if (bid && bid.schain) { - requestPayload['source'] = { - ext: { - schain: bid.schain - } - }; - } - - // Only add the user object if it's not empty - if (!isEmpty(userExt)) { - requestPayload.user = { ext: userExt }; - } - const urlQueryParams = 'src_sys=prebid'; - return { - method: 'POST', - url: URL + channelId + '?' + urlQueryParams, - data: requestPayload, - bidRequest: bidderRequest - }; - }); - - return spotxRequests; - }, - - /** - * 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) { - const bidResponses = []; - const serverResponseBody = serverResponse.body; - - if (serverResponseBody && isArray(serverResponseBody.seatbid)) { - _each(serverResponseBody.seatbid, function(bids) { - _each(bids.bid, function(spotxBid) { - let currentBidRequest = {}; - for (let i in bidderRequest.bidRequest.bids) { - if (spotxBid.impid == bidderRequest.bidRequest.bids[i].bidId) { - currentBidRequest = bidderRequest.bidRequest.bids[i]; - } - } - - /** - * Make sure currency and price are the right ones - * TODO: what about the pre_market_bid partners sizes? - */ - _each(currentBidRequest.params.pre_market_bids, function(pmb) { - if (pmb.deal_id == spotxBid.id) { - spotxBid.price = pmb.price; - serverResponseBody.cur = pmb.currency; - } - }); - - const bid = { - requestId: currentBidRequest.bidId, - currency: serverResponseBody.cur || 'USD', - cpm: spotxBid.price, - creativeId: spotxBid.crid || '', - dealId: spotxBid.dealid || '', - ttl: 360, - netRevenue: true, - channel_id: serverResponseBody.id, - mediaType: VIDEO, - width: spotxBid.w, - height: spotxBid.h - }; - - if (!!config.getConfig('cache') && !!config.getConfig('cache.url') && (config.getConfig('cache.ignoreBidderCacheKey') === true)) { - bid.vastXml = spotxBid.adm; - } else { - bid.cache_key = spotxBid.ext.cache_key; - bid.vastUrl = 'https://search.spotxchange.com/ad/vast.html?key=' + spotxBid.ext.cache_key; - bid.videoCacheKey = spotxBid.ext.cache_key; - } - - bid.meta = bid.meta || {}; - if (spotxBid && spotxBid.adomain && spotxBid.adomain.length > 0) { - bid.meta.advertiserDomains = spotxBid.adomain; - } - - const context1 = deepAccess(currentBidRequest, 'mediaTypes.video.context'); - const context2 = deepAccess(currentBidRequest, 'params.ad_unit'); - if (context1 == 'outstream' || context2 == 'outstream') { - const playersize = deepAccess(currentBidRequest, 'mediaTypes.video.playerSize'); - const renderer = Renderer.install({ - id: 0, - renderNow: true, - url: '/', - config: { - adText: 'SpotX Outstream Video Ad via Prebid.js', - player_width: playersize[0][0], - player_height: playersize[0][1], - content_page_url: deepAccess(bidderRequest, 'data.site.page'), - ad_mute: +!!deepAccess(currentBidRequest, 'params.ad_mute'), - hide_skin: +!!deepAccess(currentBidRequest, 'params.hide_skin'), - outstream_options: deepAccess(currentBidRequest, 'params.outstream_options'), - outstream_function: deepAccess(currentBidRequest, 'params.outstream_function') - } - }); - - try { - renderer.setRender(outstreamRender); - renderer.setEventHandlers({ - impression: function impression() { - return logMessage('SpotX outstream video impression event'); - }, - loaded: function loaded() { - return logMessage('SpotX outstream video loaded event'); - }, - ended: function ended() { - logMessage('SpotX outstream renderer video event'); - } - }); - } catch (err) { - logWarn('Prebid Error calling setRender or setEventHandlers on renderer', err); - } - bid.renderer = renderer; - } - - bidResponses.push(bid); - }) - }); - } - - return bidResponses; - } -} - -function createOutstreamScript(bid) { - const script = window.document.createElement('script'); - let dataSpotXParams = createScriptAttributeMap(bid); - - script.type = 'text/javascript'; - script.src = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - - setScriptAttributes(script, dataSpotXParams); - - return script; -} - -function outstreamRender(bid) { - if (bid.renderer.config.outstream_function != null && typeof bid.renderer.config.outstream_function === 'function') { - const script = createOutstreamScript(bid); - bid.renderer.config.outstream_function(bid, script); - } else { - try { - const inIframe = getBidIdParameter('in_iframe', bid.renderer.config.outstream_options); - const easiUrl = 'https://js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - let attributes = createScriptAttributeMap(bid); - if (inIframe && window.document.getElementById(inIframe).nodeName == 'IFRAME') { - const rawframe = window.document.getElementById(inIframe); - let framedoc = rawframe.contentDocument; - if (!framedoc && rawframe.contentWindow) { - framedoc = rawframe.contentWindow.document; - } - loadExternalScript(easiUrl, BIDDER_CODE, undefined, framedoc, attributes); - } else { - loadExternalScript(easiUrl, BIDDER_CODE, undefined, undefined, attributes); - } - } catch (err) { - logError('[SPOTX][renderer] Error:' + err.message); - } - } -} - -function createScriptAttributeMap(bid) { - const slot = getBidIdParameter('slot', bid.renderer.config.outstream_options); - logMessage('[SPOTX][renderer] Handle SpotX outstream renderer'); - let dataSpotXParams = {}; - dataSpotXParams['data-spotx_channel_id'] = '' + bid.channel_id; - dataSpotXParams['data-spotx_vast_url'] = '' + bid.vastUrl; - dataSpotXParams['data-spotx_content_page_url'] = bid.renderer.config.content_page_url; - dataSpotXParams['data-spotx_ad_unit'] = 'incontent'; - - logMessage('[SPOTX][renderer] Default behavior'); - if (getBidIdParameter('ad_mute', bid.renderer.config.outstream_options)) { - dataSpotXParams['data-spotx_ad_mute'] = '1'; - } - dataSpotXParams['data-spotx_collapse'] = '0'; - dataSpotXParams['data-spotx_autoplay'] = '1'; - dataSpotXParams['data-spotx_blocked_autoplay_override_mode'] = '1'; - dataSpotXParams['data-spotx_video_slot_can_autoplay'] = '1'; - dataSpotXParams['data-spotx_content_container_id'] = slot; - - const playersizeAutoAdapt = getBidIdParameter('playersize_auto_adapt', bid.renderer.config.outstream_options); - if (playersizeAutoAdapt && isBoolean(playersizeAutoAdapt) && playersizeAutoAdapt === true) { - const ratio = bid.width && isNumber(bid.width) && bid.height && isNumber(bid.height) ? bid.width / bid.height : 4 / 3; - const slotClientWidth = window.document.getElementById(slot).clientWidth; - let playerWidth = bid.renderer.config.player_width; - let playerHeight = bid.renderer.config.player_height; - let contentWidth = 0; - let contentHeight = 0; - if (slotClientWidth < playerWidth) { - playerWidth = slotClientWidth; - playerHeight = playerWidth / ratio; - } - if (ratio <= 1) { - contentWidth = Math.round(playerHeight * ratio); - contentHeight = playerHeight; - } else { - contentWidth = playerWidth; - contentHeight = Math.round(playerWidth / ratio); - } - - dataSpotXParams['data-spotx_content_width'] = '' + contentWidth; - dataSpotXParams['data-spotx_content_height'] = '' + contentHeight; - } - - const customOverride = getBidIdParameter('custom_override', bid.renderer.config.outstream_options); - if (customOverride && isPlainObject(customOverride)) { - logMessage('[SPOTX][renderer] Custom behavior.'); - for (let name in customOverride) { - if (customOverride.hasOwnProperty(name)) { - if (name === 'channel_id' || name === 'vast_url' || name === 'content_page_url' || name === 'ad_unit') { - logWarn('[SPOTX][renderer] Custom behavior: following option cannot be overridden: ' + name); - } else { - dataSpotXParams['data-spotx_' + name] = customOverride[name]; - } - } - } - } - return dataSpotXParams; -} - -registerBidder(spec); diff --git a/modules/spotxBidAdapter.md b/modules/spotxBidAdapter.md deleted file mode 100644 index 0bd1cf71aa1..00000000000 --- a/modules/spotxBidAdapter.md +++ /dev/null @@ -1,136 +0,0 @@ -# Overview - -``` -Module Name: SpotX Bidder Adapter -Module Type: Bidder Adapter -Maintainer: teameighties@spotx.tv -``` - -# Description - -Connect to SpotX for bids. - -This adapter requires setup and approval from the SpotX team. - -# Test Parameters - Use case #1 - outstream with default rendering options -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 85394, - ad_unit: 'outstream', - outstream_options: { // Needed for the default outstream renderer - fields video_slot/content_width/content_height are mandatory - slot: 'adSlot1', - content_width: 300, - content_height: 250 - } - } - }] - }]; -``` - -# Test Parameters - Use case #2 - outstream with default rendering options + some other options -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 85394, - ad_unit: 'outstream', - outstream_options: { - slot: 'adSlot1', - custom_override: { // This option is not mandatory though used to override default renderer parameters using EASI player options in here: https://developer.spotxchange.com/content/local/docs/sdkDocs/EASI/README.md - content_width: 300, - content_height: 250, - collapse: '1', - hide_fullscreen: '1', - unmute_on_mouse: '1', - continue_out_of_view: '1', - ad_volume: '100', - content_container_id: 'video1', - hide_skin: '1', - spotx_all_google_consent: '1' - } - } - } - }] - }]; -``` - -# Test Parameters - Use case #3 - outstream with your own outstream redering function -``` - var adUnits = [{ - code: 'something', - mediaTypes: { - video: { - context: 'outstream', // 'instream' or 'outstream' - playerSize: [640, 480] - } - }, - bids: [{ - bidder: 'spotx', - params: { - channel_id: 79391, - ad_unit: 'outstream', - outstream_function: myOutstreamFunction // Override the default outstream renderer by this referenced function - } - }] - }]; -``` - -# Sample of a custom outstream rendering function -``` -function myOutstreamFunction(bid) { - const videoDiv = 'video1'; - const playerWidth = 300; - const playerHeight = 250; - - window.console.log('[SPOTX][renderer] Handle SpotX custom outstream renderer'); - let script = window.document.createElement('script'); - script.type = 'text/javascript'; - script.src = '//js.spotx.tv/easi/v1/' + bid.channel_id + '.js'; - script.setAttribute('data-spotx_channel_id', '' + bid.channel_id); - script.setAttribute('data-spotx_vast_url', '' + bid.vastUrl); - script.setAttribute('data-spotx_content_width', playerWidth); - script.setAttribute('data-spotx_content_height', playerHeight); - script.setAttribute('data-spotx_content_page_url', bid.renderer.config.content_page_url); - if (bid.renderer.config.ad_mute) { - script.setAttribute('data-spotx_ad_mute', '0'); - } - script.setAttribute('data-spotx_ad_unit', 'incontent'); - script.setAttribute('data-spotx_collapse', '0'); - script.setAttribute('data-spotx_hide_fullscreen', '1'); - script.setAttribute('data-spotx_autoplay', '1'); - script.setAttribute('data-spotx_blocked_autoplay_override_mode', '1'); - script.setAttribute('data-spotx_video_slot_can_autoplay', '1'); - script.setAttribute('data-spotx_unmute_on_mouse', '1'); - script.setAttribute('data-spotx_click_to_replay', '1'); - script.setAttribute('data-spotx_continue_out_of_view', '1'); - script.setAttribute('data-spotx_ad_volume', '100'); - if (bid.renderer.config.inIframe && window.document.getElementById(bid.renderer.config.inIframe).nodeName == 'IFRAME') { - let rawframe = window.document.getElementById(bid.renderer.config.inIframe); - let framedoc = rawframe.contentDocument; - if (!framedoc && rawframe.contentWindow) { - framedoc = rawframe.contentWindow.document; - } - framedoc.body.appendChild(script); - } else { - window.document.getElementById(videoDiv).appendChild(script); - } -}; -``` diff --git a/modules/sspBCBidAdapter.js b/modules/sspBCBidAdapter.js index 08b25abee01..ba9af3314b0 100644 --- a/modules/sspBCBidAdapter.js +++ b/modules/sspBCBidAdapter.js @@ -1,4 +1,4 @@ -import { deepAccess, getWindowTop, isArray, logWarn } from '../src/utils.js'; +import { deepAccess, getWindowTop, isArray, logInfo, logWarn } from '../src/utils.js'; import { ajax } from '../src/ajax.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -12,7 +12,7 @@ const SYNC_URL = 'https://ssp.wp.pl/bidder/usersync'; const NOTIFY_URL = 'https://ssp.wp.pl/bidder/notify'; const GVLID = 676; const TMAX = 450; -const BIDDER_VERSION = '5.93'; +const BIDDER_VERSION = '6.00'; const DEFAULT_CURRENCY = 'PLN'; const W = window; const { navigator } = W; @@ -21,7 +21,6 @@ const adUnitsCalled = {}; const adSizesCalled = {}; const bidderRequestsMap = {}; const pageView = {}; -var consentApiVersion; /** * Native asset mapping - we use constant id per type @@ -99,7 +98,8 @@ const getNotificationPayload = bidData => { tagid: [], } bids.forEach(bid => { - const { adUnitCode, cpm, creativeId, meta, mediaType, params: bidParams, bidderRequestId, requestId, timeout } = bid; + const { adUnitCode, cpm, creativeId, meta = {}, mediaType, params: bidParams, bidderRequestId, requestId, timeout } = bid; + const { platform = 'wpartner' } = meta; const params = unpackParams(bidParams); // basic notification data @@ -107,6 +107,7 @@ const getNotificationPayload = bidData => { requestId: bidderRequestId || bidderRequestsMap[requestId], timeout: timeout || result.timeout, pvid: pageView.id, + platform } result = { ...result, ...bidBasicData } @@ -233,10 +234,9 @@ const applyUserIds = (validBidRequest, ortbRequest) => { const applyGdpr = (bidderRequest, ortbRequest) => { const { gdprConsent } = bidderRequest; if (gdprConsent) { - const { apiVersion, gdprApplies, consentString } = gdprConsent; - consentApiVersion = apiVersion; - ortbRequest.regs = Object.assign(ortbRequest.regs, { 'gdpr': gdprApplies ? 1 : 0 }); - ortbRequest.user = Object.assign(ortbRequest.user, { 'consent': consentString }); + const { gdprApplies, consentString } = gdprConsent; + ortbRequest.regs = Object.assign(ortbRequest.regs || {}, { 'gdpr': gdprApplies ? 1 : 0 }); + ortbRequest.user = Object.assign(ortbRequest.user || {}, { 'consent': consentString }); } } @@ -261,18 +261,18 @@ const getHighestFloor = (slot) => { mediaType: 'banner', size: next, currency - }); + }) || {}; return prev > currentFloor ? prev : currentFloor; }, 0); } const { floor: nativeFloor = 0 } = slot.getFloor({ mediaType: 'native', currency - }); + }) || {}; const { floor: videoFloor = 0 } = slot.getFloor({ mediaType: 'video', currency - }); + }) || {}; result.floor = Math.max(bannerFloor, nativeFloor, videoFloor); } @@ -340,7 +340,7 @@ var mapAsset = function mapAsset(paramName, paramValue) { id: id, required: required, title: { - len: len + len: len || 140 } }); break; @@ -469,7 +469,7 @@ var mapVideo = (slot, videoFromBid) => { const mapImpression = slot => { const { adUnitCode, bidderRequestId, bidId, params = {}, ortb2Imp = {} } = slot; const { id, siteId, video } = params; - const { ext = {} } = ortb2Imp; + const { instl, ext = {} } = ortb2Imp; /* store bidId <-> bidderRequestId mapping for bidWon notification @@ -497,6 +497,7 @@ const mapImpression = slot => { video: mapVideo(slot, video), tagid: adUnitCode, ext, + instl, }; // Check floorprices for this imp @@ -519,6 +520,11 @@ const isNativeAd = bid => { return bid.admNative || (bid.adm && bid.adm.match(xmlTester)); } +const isHTML = bid => { + const xmlTester = new RegExp(/^ { const { link = {}, imptrackers: impressionTrackers, jstracker } = nativeData; const { url: clickUrl, clicktrackers: clickTrackers = [] } = link; @@ -572,82 +578,6 @@ const parseNative = (nativeData, adUnitCode) => { return result; } -const renderCreative = (site, auctionId, bid, seat, request) => { - const { adLabel, id, slot, sn, page, publisherId, ref } = site; - let gam; - - const mcad = { - id: auctionId, - seat, - seatbid: [{ - bid: [bid], - }], - }; - - const mcbase = btoa(encodeURI(JSON.stringify(mcad))); - - if (bid.adm) { - // parse adm for gam config - try { - gam = JSON.parse(bid.adm).gam; - - if (!gam || !Object.keys(gam).length) { - gam = undefined; - } else { - gam.namedSizes = ['fluid']; - gam.div = 'div-gpt-ad-x01'; - gam.targeting = Object.assign(gam.targeting || {}, { - OAS_retarg: '0', - PREBID_ON: '1', - emptygaf: '0', - }); - } - - if (gam && !gam.targeting) { - gam.targeting = {}; - } - } catch (err) { - logWarn('Could not parse adm data', bid.adm); - } - } - - let adcode = ` - - - - - - - -
- - - - `; - - return adcode; -} - const spec = { code: BIDDER_CODE, gvlid: GVLID, @@ -714,21 +644,22 @@ const spec = { interpretResponse(serverResponse, request) { const { bidderRequest } = request; - const response = serverResponse.body; + const { body: response = {} } = serverResponse; + const { seatbid: responseSeat, ext: responseExt = {} } = response; + const { paapi: fledgeAuctionConfigs = [] } = responseExt; const bids = []; let site = JSON.parse(request.data).site; // get page and referer data from request site.sn = response.sn || 'mc_adapter'; // WPM site name (wp_sn) pageView.sn = site.sn; // store site_name (for syncing and notifications) - let seat; - if (response.seatbid !== undefined) { + if (responseSeat !== 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 => { + responseSeat.forEach(seatbid => { + const { seat, bid } = 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; @@ -744,7 +675,7 @@ const spec = { const { bidId } = bidRequest || {}; // get ext data from bid - const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [], dsa } = ext; + const { siteid = site.id, slotid = site.slot, pubid, adlabel, cache: creativeCache, vurls = [], dsa, platform = 'wpartner', pricepl } = ext; // update site data site = { @@ -775,8 +706,9 @@ const spec = { meta: { advertiserDomains: adomain, networkName: seat, - pricepl: ext && ext.pricepl, + pricepl, dsa, + platform, }, netRevenue: true, vurls, @@ -790,6 +722,8 @@ const spec = { bid.vastXml = serverBid.adm; bid.vastContent = serverBid.adm; bid.vastUrl = creativeCache; + + logInfo(`Bid ${bid.creativeId} is a video ad`); } else if (isNativeAd(serverBid)) { // native bid.mediaType = 'native'; @@ -803,10 +737,18 @@ const spec = { logWarn('Could not parse native data', serverBid.adm); bid.cpm = 0; } - } else { - // banner ad (default) + logInfo(`Bid ${bid.creativeId} as a native ad`); + } else if (isHTML(serverBid)) { + // banner ad (preformatted) bid.mediaType = 'banner'; - bid.ad = renderCreative(site, response.id, serverBid, seat, bidderRequest); + logInfo(`Bid ${bid.creativeId} as a preformatted banner`); + bid.ad = serverBid.adm; + } else { + // unsupported bid format - send notification and set CPM to zero + const payload = getNotificationPayload(bid); + payload.event = 'parseError'; + sendNotification(payload); + bid.cpm = 0; } if (bid.cpm > 0) { @@ -820,15 +762,14 @@ const spec = { }); } - return bids; + return fledgeAuctionConfigs.length ? { bids, fledgeAuctionConfigs } : bids; }, - getUserSyncs(syncOptions, serverResponses, gdprConsent) { + getUserSyncs(syncOptions) { let mySyncs = []; - // TODO: the check on CMP api version does not seem to make sense here. It means "always run the usersync unless an old (v1) CMP was detected". No attention is paid to the consent choices. - if (syncOptions.iframeEnabled && consentApiVersion != 1) { + if (syncOptions.iframeEnabled) { mySyncs.push({ type: 'iframe', - url: `${SYNC_URL}?tcf=${consentApiVersion}&pvid=${pageView.id}&sn=${pageView.sn}`, + url: `${SYNC_URL}?tcf=2&pvid=${pageView.id}&sn=${pageView.sn}`, }); }; return mySyncs; @@ -843,6 +784,15 @@ const spec = { } }, + onBidderError(errorData) { + const payload = getNotificationPayload(errorData); + if (payload) { + payload.event = 'parseError'; + sendNotification(payload); + return payload; + } + }, + onBidViewable(bid) { const payload = getNotificationPayload(bid); if (payload) { @@ -852,6 +802,15 @@ const spec = { } }, + onBidBillable(bid) { + const payload = getNotificationPayload(bid); + if (payload) { + payload.event = 'bidBillable'; + sendNotification(payload); + return payload; + } + }, + onBidWon(bid) { const payload = getNotificationPayload(bid); if (payload) { @@ -860,6 +819,7 @@ const spec = { return payload; } }, + }; registerBidder(spec); diff --git a/modules/ssp_genieeBidAdapter.js b/modules/ssp_genieeBidAdapter.js new file mode 100644 index 00000000000..ed15842f96b --- /dev/null +++ b/modules/ssp_genieeBidAdapter.js @@ -0,0 +1,425 @@ +/* eslint-disable camelcase */ +import * as utils from '../src/utils.js'; +import { isPlainObject } from '../src/utils.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_ANALYTICS } from '../src/activities/modules.js'; +import { highEntropySUAAccessor } from '../src/fpd/sua.js'; +import { config } from '../src/config.js'; + +/** + * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest + * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse + */ + +const BIDDER_CODE = 'ssp_geniee'; +export const BANNER_ENDPOINT = 'https://aladdin.genieesspv.jp/yie/ld/api/ad_call/v2'; +// export const ENDPOINT_USERSYNC = ''; +const SUPPORTED_MEDIA_TYPES = [ BANNER ]; +const DEFAULT_CURRENCY = 'JPY'; +const ALLOWED_CURRENCIES = ['USD', 'JPY']; +const NET_REVENUE = true; +const MODULE_NAME = `ssp_geniee`; +export const storage = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_NAME}) + +/** + * List of keys for geparams (parameters we use) + * key: full name of the parameter + * value: shortened name used in geparams + */ +const GEPARAMS_KEY = { + /** + * location.href whose protocol is not http + */ + LOCATION: 'loc', + /** + * document.referrer whose protocol is not http + */ + REFERRER: 'ref', + /** + * URL parameter to be linked to clicks + */ + GENIEE_CT0: 'ct0', + /** + * zipcode + */ + ZIP: 'zip', + /** + * country + */ + COUNTRY: 'country', + /** + * city + */ + CITY: 'city', + /** + * longitude + */ + LONGITUDE: 'long', + /** + * lattitude + */ + LATITUDE: 'lati', + /** + * for customised parameters + */ + CUSTOM: 'custom', + /** + * advertising identifier for iOS + */ + IDENTIFIER_FOR_ADVERTISERS: 'idfa', + /** + * tracked Ad restrictions for iOS + */ + LIMIT_AD_TRACKING: 'lat', + /** + * bundle ID of iOS applications? + */ + BUNDLE: 'bundle', +}; + +/** + * List of keys for gecuparams (parameters we use) + * key: full name of the parameter + * value: shortened name used in geparams + */ +const GECUPARAMS_KEY = { + /** + * version no of gecuparams + */ + VERSION: 'ver', + /** + * minor version no of gecuparams + */ + MINOR_VERSION: 'minor', + /** + * encrypted value of LTSV format + */ + VALUE: 'value', +}; + +/** + * executing encodeURIComponent including single quotation + * @param {string} str + * @returns + */ +function encodeURIComponentIncludeSingleQuotation(str) { + return encodeURIComponent(str).replace(/'/g, '%27'); +} + +/** + * Checking "params" has a value for the key "key" and it is not undefined, null, or an empty string + * To support IE in the same way, we cannot use the ?? operator + * @param {Object} params + * @param {string} key + * @returns {boolean} + */ +function hasParamsNotBlankString(params, key) { + return ( + key in params && + typeof params[key] !== 'undefined' && + params[key] != null && + params[key] != '' + ); +} + +/** + * making request data be used commonly banner and native + * @see https://docs.prebid.org/dev-docs/bidder-adaptor.html#location-and-referrers + */ +function makeCommonRequestData(bid, geparameter, refererInfo) { + const data = { + zoneid: bid.params.zoneId, + cb: Math.floor(Math.random() * 99999999999), + charset: document.charset || document.characterSet || '', + loc: refererInfo?.page || refererInfo?.location || refererInfo?.topmostLocation || refererInfo?.legacy.referer || encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.LOCATION]) || '', + ct0: geparameter[GEPARAMS_KEY.GENIEE_CT0] !== 'undefined' + ? encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.GENIEE_CT0]) + : '', + referer: refererInfo?.ref || encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.REFERRER]) || '', + topframe: window.parent == window.self ? 1 : 0, + cur: bid.params.hasOwnProperty('currency') ? bid.params.currency : DEFAULT_CURRENCY, + requestid: bid.bidId, + ua: navigator.userAgent, + tpaf: 1, + cks: 1, + ib: 0, + }; + + try { + if (window.self.toString() !== '[object Window]' || window.parent.toString() !== '[object Window]') { + data.err = '1'; + } + } catch (e) {} + + if (GEPARAMS_KEY.IDENTIFIER_FOR_ADVERTISERS in geparameter) { + data.idfa = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.IDENTIFIER_FOR_ADVERTISERS]); + } + if (GEPARAMS_KEY.LIMIT_AD_TRACKING in geparameter) { + data.adtk = geparameter[GEPARAMS_KEY.LIMIT_AD_TRACKING] ? '0' : '1'; + } + // makeScreenSizeForQueryParameter + if (typeof screen !== 'undefined') { + const screenWidth = screen.width; + const screenHeight = screen.height; + if (screenWidth > screenHeight) { + data.sw = screenHeight; + data.sh = screenWidth; + } else { + data.sw = screenWidth; + data.sh = screenHeight; + } + } + // makeBannerJskQuery + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.ZIP)) { + data.zip = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.ZIP]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.COUNTRY)) { + data.country = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.COUNTRY]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.CITY)) { + data.city = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.CITY]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.LONGITUDE)) { + data.long = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.LONGITUDE]); + } + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.LATITUDE)) { + data.lati = encodeURIComponentIncludeSingleQuotation( + geparameter[GEPARAMS_KEY.LATITUDE] + ); + } + if (GEPARAMS_KEY.CUSTOM in geparameter && isPlainObject(geparameter[GEPARAMS_KEY.CUSTOM])) { + for (const c in geparameter[GEPARAMS_KEY.CUSTOM]) { + if (hasParamsNotBlankString(geparameter[GEPARAMS_KEY.CUSTOM], c)) { + data[encodeURIComponentIncludeSingleQuotation('custom_' + c)] = + encodeURIComponentIncludeSingleQuotation( + geparameter[GEPARAMS_KEY.CUSTOM][c] + ); + } + } + } + const gecuparameter = window.gecuparams || {}; + if (isPlainObject(gecuparameter)) { + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.VERSION)) { + data.gc_ver = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.VERSION]); + } + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.MINOR_VERSION)) { + data.gc_minor = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.MINOR_VERSION]); + } + if (hasParamsNotBlankString(gecuparameter, GECUPARAMS_KEY.VALUE)) { + data.gc_value = encodeURIComponentIncludeSingleQuotation(gecuparameter[GECUPARAMS_KEY.VALUE]); + } + } + + // imuid + const imuidQuery = getImuidAsQueryParameter(bid); + if (imuidQuery) data.extuid = imuidQuery; + + // makeUAQuery + // To avoid double encoding, not using encodeURIComponent here + const ua = JSON.parse(getUserAgent()); + if (ua && ua.fullVersionList) { + const fullVersionList = ua.fullVersionList.reduce((acc, cur) => { + let str = acc; + if (str) str += ','; + str += '"' + cur.brand + '";v="' + cur.version + '"'; + return str; + }, ''); + data.ucfvl = fullVersionList; + } + if (ua && ua.platform) data.ucp = '"' + ua.platform + '"'; + if (ua && ua.architecture) data.ucarch = '"' + ua.architecture + '"'; + if (ua && ua.platformVersion) data.ucpv = '"' + ua.platformVersion + '"'; + if (ua && ua.bitness) data.ucbit = '"' + ua.bitness + '"'; + data.ucmbl = '?' + (ua && ua.mobile ? '1' : '0'); + if (ua && ua.model) data.ucmdl = '"' + ua.model + '"'; + + return data; +} + +/** + * making request data for banner + */ +function makeBannerRequestData(bid, geparameter, refererInfo) { + const data = makeCommonRequestData(bid, geparameter, refererInfo); + + // this query is not used in nad endpoint but used in ad_call endpoint + if (hasParamsNotBlankString(geparameter, GEPARAMS_KEY.BUNDLE)) { + data.apid = encodeURIComponentIncludeSingleQuotation(geparameter[GEPARAMS_KEY.BUNDLE]); + } + + return data; +} + +/** + * making bid response be used commonly banner and native + */ +function makeCommonBidResponse(bid, width, height) { + return { + requestId: bid.requestid, + cpm: bid.price, + creativeId: bid.creativeId, + currency: bid.cur, + netRevenue: NET_REVENUE, + ttl: 700, + width: width, // width of the ad iframe + height: height, // height of the ad iframe + }; +} + +/** + * making bid response for banner + */ +function makeBannerBidResponse(bid, request) { + const bidResponse = makeCommonBidResponse(bid, bid.width, bid.height); + const loc = encodeURIComponentIncludeSingleQuotation( + window.top === window.self ? location.href : window.top.document.referrer + ); + const beacon = !bid.ib + ? '' + : ` +
+ +
`; + bidResponse.ad = makeBidResponseAd( + beacon + '
' + makeChangeHeightEventMarkup(request) + decodeURIComponent(bid.adm) + '
' + ); + bidResponse.mediaType = BANNER; + + return bidResponse; +} + +/** + * making change height event markup for af iframe. About passback ad, it is possible that ad image is cut off. To handle this, we add this event to change height after ad is loaded. + */ +function makeChangeHeightEventMarkup(request) { + return ( + '' + ); +} + +/** + * making bid response ad. This is also the value to be used by document.write in renderAd function. + * @param {string} innerHTML + * @returns + */ +function makeBidResponseAd(innerHTML) { + return '' + innerHTML + ''; +} + +/** + * return imuid strings as query parameters + */ +function getImuidAsQueryParameter(bid) { + const imuid = utils.deepAccess(bid, 'userId.imuid'); + return imuid ? 'im:' + imuid : ''; // To avoid double encoding, not using encodeURIComponent here +} + +function getUserAgent() { + return storage.getDataFromLocalStorage('key') || null; +} + +export const spec = { + code: BIDDER_CODE, + supportedMediaTypes: SUPPORTED_MEDIA_TYPES, + /** + * 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) { + if (!bidRequest.params.zoneId) return false; + const currencyType = config.getConfig('currency.adServerCurrency'); + if (typeof currencyType === 'string' && ALLOWED_CURRENCIES.indexOf(currencyType) === -1) { + utils.logError('Invalid currency type, we support only JPY and USD!'); + return false; + } + return true; + }, + /** + * 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, bidderRequest) { + const serverRequests = []; + + const HIGH_ENTROPY_HINTS = [ + 'architecture', + 'model', + 'mobile', + 'platform', + 'bitness', + 'platformVersion', + 'fullVersionList', + ]; + + const uaData = window.navigator?.userAgentData; + if (uaData && uaData.getHighEntropyValues) { + const getHighEntropySUA = highEntropySUAAccessor(uaData); + getHighEntropySUA(HIGH_ENTROPY_HINTS).then((ua) => { + if (ua) { + storage.setDataInLocalStorage('ua', JSON.stringify(ua)); + } + }); + } + + validBidRequests.forEach((bid) => { + // const isNative = bid.mediaTypes?.native; + const geparameter = window.geparams || {}; + + serverRequests.push({ + method: 'GET', + url: BANNER_ENDPOINT, + data: makeBannerRequestData(bid, geparameter, bidderRequest?.refererInfo), + bid: bid, + }); + }); + + 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 bidResponses = []; + + if (!serverResponse || !serverResponse.body) { + return bidResponses; + } + + const zoneId = bidderRequest.bid.params.zoneId; + let successBid; + successBid = serverResponse.body || {}; + + if (successBid.hasOwnProperty(zoneId)) { + const bid = successBid[zoneId]; + bidResponses.push(makeBannerBidResponse(bid, bidderRequest)); + } + return bidResponses; + }, + getUserSyncs: function (syncOptions, serverResponses) { + const syncs = []; + + // if we need user sync, we add this part after preparing the endpoint + /* if (syncOptions.pixelEnabled) { + syncs.push({ + type: 'image', + url: ENDPOINT_USERSYNC + }); + } */ + + return syncs; + }, + onTimeout: function (timeoutData) {}, + onBidWon: function (bid) {}, + onSetTargeting: function (bid) {}, +}; + +registerBidder(spec); diff --git a/modules/ssp_genieeBidAdapter.md b/modules/ssp_genieeBidAdapter.md new file mode 100644 index 00000000000..fe77a6f858b --- /dev/null +++ b/modules/ssp_genieeBidAdapter.md @@ -0,0 +1,43 @@ +# Overview + +``` +Module Name: Geniee Bid Adapter +Module Type: Bidder Adapter +Maintainer: supply-carpet@geniee.co.jp +``` + +# Description +This is [Geniee](https://geniee.co.jp) Bidder Adapter for Prebid.js. +(This is Geniee *SSP* Bidder Adapter. The another adapter named "Geniee Bid Adapter" is Geniee *DSP* Bidder Adapter.) + +Please contact us before using the adapter. + +We will provide ads when satisfy the following conditions: + +- There are a certain number bid requests by zone +- The request is a Banner ad +- Payment is possible in Japanese yen or US dollars +- The request is not for GDPR or COPPA users + +Thus, even if the following test, it will be no bids if the request does not reach a certain requests. + +# Test Parameters + +```js +var adUnits = [ + { + code: 'banner-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + bids: [{ + bidder: 'ssp_geniee', + params: { + zoneId: 1573195 + } + }] + }, +]; +``` diff --git a/modules/staqAnalyticsAdapter.js b/modules/staqAnalyticsAdapter.js deleted file mode 100644 index ac5e86db19d..00000000000 --- a/modules/staqAnalyticsAdapter.js +++ /dev/null @@ -1,433 +0,0 @@ -import { logInfo, logError, parseUrl, _each } from '../src/utils.js'; -import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; -import { EVENTS } from '../src/constants.js'; -import adapterManager from '../src/adapterManager.js'; -import { getRefererInfo } from '../src/refererDetection.js'; -import { ajax } from '../src/ajax.js'; -import {getStorageManager} from '../src/storageManager.js'; -import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; - -const MODULE_CODE = 'staq'; -const storageObj = getStorageManager({moduleType: MODULE_TYPE_ANALYTICS, moduleName: MODULE_CODE}); - -const ANALYTICS_VERSION = '1.0.0'; -const DEFAULT_QUEUE_TIMEOUT = 4000; -const DEFAULT_HOST = 'tag.staq.com'; - -let staqAdapterRefWin; - -const STAQ_EVENTS = { - AUCTION_INIT: 'auctionInit', - BID_REQUEST: 'bidRequested', - BID_RESPONSE: 'bidResponse', - BID_WON: 'bidWon', - AUCTION_END: 'auctionEnd', - TIMEOUT: 'adapterTimedOut' -}; - -function buildRequestTemplate(connId) { - // TODO: what should these pick from refererInfo? - const url = staqAdapterRefWin.topmostLocation; - const ref = staqAdapterRefWin.topmostLocation; - const topLocation = staqAdapterRefWin.topmostLocation; - - return { - ver: ANALYTICS_VERSION, - domain: topLocation.hostname, - path: topLocation.pathname, - userAgent: navigator.userAgent, - connId: connId, - env: { - screen: { - w: window.screen.width, - h: window.screen.height - }, - lang: navigator.language - }, - src: getUmtSource(url, ref) - } -} - -let analyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { - track({ eventType, args }) { - if (!analyticsAdapter.context) { - return; - } - let handler = null; - switch (eventType) { - case EVENTS.AUCTION_INIT: - if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.init(); - } - handler = trackAuctionInit; - break; - case EVENTS.BID_REQUESTED: - handler = trackBidRequest; - break; - case EVENTS.BID_RESPONSE: - handler = trackBidResponse; - break; - case EVENTS.BID_WON: - handler = trackBidWon; - break; - case EVENTS.BID_TIMEOUT: - handler = trackBidTimeout; - break; - case EVENTS.AUCTION_END: - handler = trackAuctionEnd; - break; - } - if (handler) { - let events = handler(args); - if (analyticsAdapter.context.queue) { - analyticsAdapter.context.queue.push(events); - if (eventType === EVENTS.BID_WON) { - analyticsAdapter.context.queue.updateWithWins(events); - } - } - if (eventType === EVENTS.AUCTION_END) { - sendAll(); - } - } - } -}); - -analyticsAdapter.context = {}; - -analyticsAdapter.originEnableAnalytics = analyticsAdapter.enableAnalytics; - -analyticsAdapter.enableAnalytics = (config) => { - logInfo('Enabling STAQ Adapter'); - staqAdapterRefWin = getRefererInfo(window); - if (!config.options.connId) { - logError('ConnId is not defined. STAQ Analytics won\'t work'); - return; - } - if (!config.options.url) { - logError('URL is not defined. STAQ Analytics won\'t work'); - return; - } - analyticsAdapter.context = { - host: config.options.host || DEFAULT_HOST, - url: config.options.url, - connectionId: config.options.connId, - requestTemplate: buildRequestTemplate(config.options.connId), - queue: new ExpiringQueue(sendAll, config.options.queueTimeout || DEFAULT_QUEUE_TIMEOUT) - }; - analyticsAdapter.originEnableAnalytics(config); -}; - -adapterManager.registerAnalyticsAdapter({ - adapter: analyticsAdapter, - code: MODULE_CODE, -}); - -export default analyticsAdapter; - -function sendAll() { - let events = analyticsAdapter.context.queue.popAll(); - if (events.length !== 0) { - let req = analyticsAdapter.context.requestTemplate; - req.auctionId = analyticsAdapter.context.auctionId; - req.events = events - - analyticsAdapter.ajaxCall(JSON.stringify(req)); - } -} - -analyticsAdapter.ajaxCall = function ajaxCall(data) { - logInfo('SENDING DATA: ' + data); - ajax(`https://${analyticsAdapter.context.url}/prebid/${analyticsAdapter.context.connectionId}`, () => {}, data, { contentType: 'text/plain' }); -}; - -function trackAuctionInit(args) { - analyticsAdapter.context.auctionTimeStart = Date.now(); - analyticsAdapter.context.auctionId = args.auctionId; - const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_INIT); - return [event]; -} - -function trackBidRequest(args) { - return args.bids.map(bid => - createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_REQUEST, bid.adUnitCode)); -} - -function trackBidResponse(args) { - const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_RESPONSE, - args.adUnitCode, args.cpm, args.timeToRespond / 1000, false, args); - return [event]; -} - -function trackBidWon(args) { - const event = createHbEvent(args.auctionId, args.bidderCode, STAQ_EVENTS.BID_WON, args.adUnitCode, args.cpm, undefined, true, args); - return [event]; -} - -function trackAuctionEnd(args) { - const event = createHbEvent(args.auctionId, undefined, STAQ_EVENTS.AUCTION_END, undefined, - undefined, (Date.now() - analyticsAdapter.context.auctionTimeStart) / 1000); - return [event]; -} - -function trackBidTimeout(args) { - return args.map(arg => createHbEvent(arg.auctionId, arg.bidderCode, STAQ_EVENTS.TIMEOUT)); -} - -function createHbEvent(auctionId, adapter, event, adUnitCode = undefined, value = 0, time = 0, bidWon = undefined, eventArgs) { - let ev = { event: event }; - if (adapter) { - ev.adapter = adapter; - ev.bidderName = adapter; - } - if (adUnitCode) { - ev.adUnitCode = adUnitCode; - } - if (value) { - ev.cpm = value; - } - if (time) { - ev.timeToRespond = time; - } - if (typeof bidWon !== 'undefined') { - ev.bidWon = bidWon; - } else if (event === 'bidResponse') { - ev.bidWon = false; - } - ev.auctionId = auctionId; - - if (eventArgs) { - if (STAQ_EVENTS.BID_RESPONSE == event || STAQ_EVENTS.BID_WON == event) { - ev.width = eventArgs.width; - ev.height = eventArgs.height; - - ev.adId = eventArgs.adId; - } - } - - return ev; -} - -const UTM_TAGS = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', - 'utm_c1', 'utm_c2', 'utm_c3', 'utm_c4', 'utm_c5' -]; -const STAQ_PREBID_KEY = 'staq_analytics'; -const DIRECT = '(direct)'; -const REFERRAL = '(referral)'; -const ORGANIC = '(organic)'; - -export let storage = { - getItem: (name) => { - return storageObj.getDataFromLocalStorage(name); - }, - setItem: (name, value) => { - storageObj.setDataInLocalStorage(name, value); - } -}; - -export function getUmtSource(pageUrl, referrer) { - let prevUtm = getPreviousTrafficSource(); - let currUtm = getCurrentTrafficSource(pageUrl, referrer); - let [updated, actual] = chooseActualUtm(prevUtm, currUtm); - if (updated) { - storeUtm(actual); - } - return actual; - - function getPreviousTrafficSource() { - let val = storage.getItem(STAQ_PREBID_KEY); - if (!val) { - return getDirect(); - } - return JSON.parse(val); - } - - function getCurrentTrafficSource(pageUrl, referrer) { - var source = getUTM(pageUrl); - if (source) { - return source; - } - if (referrer) { - let se = getSearchEngine(referrer); - if (se) { - return asUtm(se, ORGANIC, ORGANIC); - } - let parsedUrl = parseUrl(pageUrl); - let [refHost, refPath] = getReferrer(referrer); - if (refHost && refHost !== parsedUrl.hostname) { - return asUtm(refHost, REFERRAL, REFERRAL, '', refPath); - } - } - return getDirect(); - } - - function getSearchEngine(pageUrl) { - let engines = { - 'google': /^https?\:\/\/(?:www\.)?(?:google\.(?:com?\.)?(?:com|cat|[a-z]{2})|g.cn)\//i, - 'yandex': /^https?\:\/\/(?:www\.)?ya(?:ndex\.(?:com|net)?\.?(?:asia|mobi|org|[a-z]{2})?|\.ru)\//i, - 'bing': /^https?\:\/\/(?:www\.)?bing\.com\//i, - 'duckduckgo': /^https?\:\/\/(?:www\.)?duckduckgo\.com\//i, - 'ask': /^https?\:\/\/(?:www\.)?ask\.com\//i, - 'yahoo': /^https?\:\/\/(?:[-a-z]+\.)?(?:search\.)?yahoo\.com\//i - }; - - for (let engine in engines) { - if (engines.hasOwnProperty(engine) && engines[engine].test(pageUrl)) { - return engine; - } - } - } - - function getReferrer(referrer) { - let ref = parseUrl(referrer); - return [ref.hostname, ref.pathname]; - } - - function getUTM(pageUrl) { - let urlParameters = parseUrl(pageUrl).search; - if (!urlParameters['utm_campaign'] || !urlParameters['utm_source']) { - return; - } - let utmArgs = []; - _each(UTM_TAGS, (utmTagName) => { - let utmValue = urlParameters[utmTagName] || ''; - utmArgs.push(utmValue); - }); - return asUtm.apply(this, utmArgs); - } - - function getDirect() { - return asUtm(DIRECT, DIRECT, DIRECT); - } - - function storeUtm(utm) { - let val = JSON.stringify(utm); - storage.setItem(STAQ_PREBID_KEY, val); - } - - function asUtm(source, medium, campaign, term = '', content = '', c1 = '', c2 = '', c3 = '', c4 = '', c5 = '') { - let result = { - source: source, - medium: medium, - campaign: campaign - }; - if (term) { - result.term = term; - } - if (content) { - result.content = content; - } - if (c1) { - result.c1 = c1; - } - if (c2) { - result.c2 = c2; - } - if (c3) { - result.c3 = c3; - } - if (c4) { - result.c4 = c4; - } - if (c5) { - result.c5 = c5; - } - return result; - } - - function chooseActualUtm(prev, curr) { - if (ord(prev) < ord(curr)) { - return [true, curr]; - } - if (ord(prev) > ord(curr)) { - return [false, prev]; - } else { - if (prev.campaign === REFERRAL && prev.content !== curr.content) { - return [true, curr]; - } else if (prev.campaign === ORGANIC && prev.source !== curr.source) { - return [true, curr]; - } else if (isCampaignTraffic(prev) && (prev.campaign !== curr.campaign || prev.source !== curr.source)) { - return [true, curr]; - } - } - return [false, prev]; - } - - function ord(utm) { - switch (utm.campaign) { - case DIRECT: - return 0; - case ORGANIC: - return 1; - case REFERRAL: - return 2; - default: - return 3; - } - } - - function isCampaignTraffic(utm) { - return [DIRECT, REFERRAL, ORGANIC].indexOf(utm.campaign) === -1; - } -} - -/** - * Expiring queue implementation. Fires callback on elapsed timeout since last last update or creation. - * @param callback - * @param ttl - * @constructor - */ -export function ExpiringQueue(callback, ttl) { - let queue = []; - let timeoutId; - - this.push = (event) => { - if (event instanceof Array) { - queue.push.apply(queue, event); - } else { - queue.push(event); - } - reset(); - }; - - this.updateWithWins = (winEvents) => { - winEvents.forEach(winEvent => { - queue.forEach(prevEvent => { - if (prevEvent.event === 'bidResponse' && - prevEvent.auctionId == winEvent.auctionId && - prevEvent.adUnitCode == winEvent.adUnitCode && - prevEvent.adId == winEvent.adId && - prevEvent.adapter == winEvent.adapter) { - prevEvent.bidWon = true; - } - }); - }); - } - - this.popAll = () => { - let result = queue; - queue = []; - reset(); - return result; - }; - - /** - * For test/debug purposes only - * @return {Array} - */ - this.peekAll = () => { - return queue; - }; - - this.init = reset; - - function reset() { - if (timeoutId) { - clearTimeout(timeoutId); - } - timeoutId = setTimeout(() => { - if (queue.length) { - callback(); - } - }, ttl); - } -} diff --git a/modules/stnBidAdapter.js b/modules/stnBidAdapter.js index 42b69ee7c2b..ba922c0fd57 100644 --- a/modules/stnBidAdapter.js +++ b/modules/stnBidAdapter.js @@ -2,33 +2,32 @@ import { logWarn, logInfo, isArray, - isFn, deepAccess, - isEmpty, - contains, - timestamp, triggerPixel, - isInteger, - getBidIdParameter } from '../src/utils.js'; -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {BANNER, VIDEO} from '../src/mediaTypes.js'; -import {config} from '../src/config.js'; - -const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; -const BIDDER_CODE = 'stn'; -const ADAPTER_VERSION = '6.0.0'; -const TTL = 360; -const DEFAULT_CURRENCY = 'USD'; -const SELLER_ENDPOINT = 'https://hb.stngo.com/'; -const MODES = { +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { BANNER, VIDEO } from '../src/mediaTypes.js'; +import { + getEndpoint, + generateBidsParams, + generateGeneralParams, + buildBidResponse, +} from '../libraries/riseUtils/index.js'; + +export const SUPPORTED_AD_TYPES = [BANNER, VIDEO]; +export const BIDDER_CODE = 'stn'; +export const ADAPTER_VERSION = '6.1.0'; +export const TTL = 360; +export const DEFAULT_CURRENCY = 'USD'; +export const SELLER_ENDPOINT = 'https://hb.stngo.com/'; +export const MODES = { PRODUCTION: 'hb-multi', TEST: 'hb-multi-test' -} -const SUPPORTED_SYNC_METHODS = { +}; +export const SUPPORTED_SYNC_METHODS = { IFRAME: 'iframe', PIXEL: 'pixel' -} +}; export const spec = { code: BIDDER_CODE, @@ -50,7 +49,6 @@ export const spec = { buildRequests: function (validBidRequests, bidderRequest) { const combinedRequestsObject = {}; - // use data from the first bid, to create the general params for all bids const generalObject = validBidRequests[0]; const testMode = generalObject.params.testMode; @@ -59,41 +57,16 @@ export const spec = { return { method: 'POST', - url: getEndpoint(testMode), + url: getEndpoint(testMode, SELLER_ENDPOINT, MODES), data: combinedRequestsObject } }, - interpretResponse: function ({body}) { + interpretResponse: function ({ body }) { const bidResponses = []; if (body.bids) { body.bids.forEach(adUnit => { - const bidResponse = { - requestId: adUnit.requestId, - cpm: adUnit.cpm, - currency: adUnit.currency || DEFAULT_CURRENCY, - width: adUnit.width, - height: adUnit.height, - ttl: adUnit.ttl || TTL, - creativeId: adUnit.requestId, - netRevenue: adUnit.netRevenue || true, - nurl: adUnit.nurl, - mediaType: adUnit.mediaType, - meta: { - mediaType: adUnit.mediaType - } - }; - - if (adUnit.mediaType === VIDEO) { - bidResponse.vastXml = adUnit.vastXml; - } else if (adUnit.mediaType === BANNER) { - bidResponse.ad = adUnit.ad; - } - - if (adUnit.adomain && adUnit.adomain.length) { - bidResponse.meta.advertiserDomains = adUnit.adomain; - } - + const bidResponse = buildBidResponse(adUnit, DEFAULT_CURRENCY, TTL, VIDEO, BANNER); bidResponses.push(bidResponse); }); } @@ -134,352 +107,3 @@ export const spec = { }; registerBidder(spec); - -/** - * Get floor price - * @param bid {bid} - * @param mediaType {String} - * @returns {Number} - */ -function getFloor(bid, mediaType) { - if (!isFn(bid.getFloor)) { - return 0; - } - let floorResult = bid.getFloor({ - currency: DEFAULT_CURRENCY, - mediaType: mediaType, - size: '*' - }); - return floorResult.currency === DEFAULT_CURRENCY && floorResult.floor ? floorResult.floor : 0; -} - -/** - * Get the ad sizes array from the bid - * @param bid {bid} - * @param mediaType {String} - * @returns {Array} - */ -function getSizesArray(bid, mediaType) { - let sizesArray = [] - - if (deepAccess(bid, `mediaTypes.${mediaType}.sizes`)) { - sizesArray = bid.mediaTypes[mediaType].sizes; - } else if (Array.isArray(bid.sizes) && bid.sizes.length > 0) { - sizesArray = bid.sizes; - } - - return sizesArray; -} - -/** - * Get schain string value - * @param schainObject {Object} - * @returns {string} - */ -function getSupplyChain(schainObject) { - if (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 (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; -} - -/** - * Get preferred user-sync method based on publisher configuration - * @param filterSettings {Object} - * @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 = isArray(syncRule.bidders) ? syncRule.bidders : [bidderCode]; - return isInclude && 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 ua {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'; -} - -function generateBidsParams(validBidRequests, bidderRequest) { - const bidsArray = []; - - if (validBidRequests.length) { - validBidRequests.forEach(bid => { - bidsArray.push(generateBidParameters(bid, bidderRequest)); - }); - } - - return bidsArray; -} - -/** - * Generate bid specific parameters - * @param {bid} bid - * @param {bidderRequest} bidderRequest - * @returns {Object} bid specific params object - */ -function generateBidParameters(bid, bidderRequest) { - const {params} = bid; - const mediaType = isBanner(bid) ? BANNER : VIDEO; - const sizesArray = getSizesArray(bid, mediaType); - - // fix floor price in case of NAN - if (isNaN(params.floorPrice)) { - params.floorPrice = 0; - } - - const bidObject = { - mediaType, - adUnitCode: getBidIdParameter('adUnitCode', bid), - sizes: sizesArray, - floorPrice: Math.max(getFloor(bid, mediaType), params.floorPrice), - bidId: getBidIdParameter('bidId', bid), - loop: getBidIdParameter('bidderRequestsCount', bid), - bidderRequestId: getBidIdParameter('bidderRequestId', bid), - transactionId: bid.ortb2Imp?.ext?.tid || '', - coppa: 0, - }; - - const pos = deepAccess(bid, `mediaTypes.${mediaType}.pos`); - if (pos) { - bidObject.pos = pos; - } - - const gpid = deepAccess(bid, `ortb2Imp.ext.gpid`); - if (gpid) { - bidObject.gpid = gpid; - } - - const placementId = params.placementId || deepAccess(bid, `mediaTypes.${mediaType}.name`); - if (placementId) { - bidObject.placementId = placementId; - } - - const mimes = deepAccess(bid, `mediaTypes.${mediaType}.mimes`); - if (mimes) { - bidObject.mimes = mimes; - } - const api = deepAccess(bid, `mediaTypes.${mediaType}.api`); - if (api) { - bidObject.api = api; - } - - const sua = deepAccess(bid, `ortb2.device.sua`); - if (sua) { - bidObject.sua = sua; - } - - const coppa = deepAccess(bid, `ortb2.regs.coppa`) - if (coppa) { - bidObject.coppa = 1; - } - - if (mediaType === VIDEO) { - const playbackMethod = deepAccess(bid, `mediaTypes.video.playbackmethod`); - let playbackMethodValue; - - // verify playbackMethod is of type integer array, or integer only. - if (Array.isArray(playbackMethod) && isInteger(playbackMethod[0])) { - // only the first playbackMethod in the array will be used, according to OpenRTB 2.5 recommendation - playbackMethodValue = playbackMethod[0]; - } else if (isInteger(playbackMethod)) { - playbackMethodValue = playbackMethod; - } - - if (playbackMethodValue) { - bidObject.playbackMethod = playbackMethodValue; - } - - const placement = deepAccess(bid, `mediaTypes.video.placement`); - if (placement) { - bidObject.placement = placement; - } - - const minDuration = deepAccess(bid, `mediaTypes.video.minduration`); - if (minDuration) { - bidObject.minDuration = minDuration; - } - - const maxDuration = deepAccess(bid, `mediaTypes.video.maxduration`); - if (maxDuration) { - bidObject.maxDuration = maxDuration; - } - - const skip = deepAccess(bid, `mediaTypes.video.skip`); - if (skip) { - bidObject.skip = skip; - } - - const linearity = deepAccess(bid, `mediaTypes.video.linearity`); - if (linearity) { - bidObject.linearity = linearity; - } - - const protocols = deepAccess(bid, `mediaTypes.video.protocols`); - if (protocols) { - bidObject.protocols = protocols; - } - - const plcmt = deepAccess(bid, `mediaTypes.video.plcmt`); - if (plcmt) { - bidObject.plcmt = plcmt; - } - } - - return bidObject; -} - -function isBanner(bid) { - return bid.mediaTypes && bid.mediaTypes.banner; -} - -/** - * Generate params that are common between all bids - * @param {single bid object} generalObject - * @param {bidderRequest} bidderRequest - * @returns {object} the common params object - */ -function generateGeneralParams(generalObject, bidderRequest) { - const domain = window.location.hostname; - const {syncEnabled, filterSettings} = config.getConfig('userSync') || {}; - const {bidderCode} = bidderRequest; - const generalBidParams = generalObject.params; - const timeout = bidderRequest.timeout; - - // these params are snake_case instead of camelCase to allow backwards compatability on the server. - // in the future, these will be converted to camelCase to match our convention. - const generalParams = { - wrapper_type: 'prebidjs', - wrapper_vendor: '$$PREBID_GLOBAL$$', - wrapper_version: '$prebid.version$', - adapter_version: ADAPTER_VERSION, - auction_start: timestamp(), - publisher_id: generalBidParams.org, - publisher_name: domain, - site_domain: domain, - dnt: (navigator.doNotTrack === 'yes' || navigator.doNotTrack === '1' || navigator.msDoNotTrack === '1') ? 1 : 0, - device_type: getDeviceType(navigator.userAgent), - ua: navigator.userAgent, - is_wrapper: !!generalBidParams.isWrapper, - session_id: generalBidParams.sessionId || getBidIdParameter('bidderRequestId', generalObject), - tmax: timeout - } - - const userIdsParam = getBidIdParameter('userId', generalObject); - if (userIdsParam) { - generalParams.userIds = JSON.stringify(userIdsParam); - } - - const ortb2Metadata = bidderRequest.ortb2 || {}; - if (ortb2Metadata.site) { - generalParams.site_metadata = JSON.stringify(ortb2Metadata.site); - } - if (ortb2Metadata.user) { - generalParams.user_metadata = JSON.stringify(ortb2Metadata.user); - } - - if (syncEnabled) { - const allowedSyncMethod = getAllowedSyncMethod(filterSettings, bidderCode); - if (allowedSyncMethod) { - generalParams.cs_method = allowedSyncMethod; - } - } - - if (bidderRequest.uspConsent) { - generalParams.us_privacy = bidderRequest.uspConsent; - } - - if (bidderRequest && bidderRequest.gdprConsent && bidderRequest.gdprConsent.gdprApplies) { - generalParams.gdpr = bidderRequest.gdprConsent.gdprApplies; - generalParams.gdpr_consent = bidderRequest.gdprConsent.consentString; - } - - if (bidderRequest.gppConsent) { - generalParams.gpp = bidderRequest.gppConsent.gppString; - generalParams.gpp_sid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - generalParams.gpp = bidderRequest.ortb2.regs.gpp; - generalParams.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (generalBidParams.ifa) { - generalParams.ifa = generalBidParams.ifa; - } - - if (generalObject.schain) { - generalParams.schain = getSupplyChain(generalObject.schain); - } - - if (bidderRequest && bidderRequest.refererInfo) { - generalParams.referrer = deepAccess(bidderRequest, 'refererInfo.ref'); - generalParams.page_url = deepAccess(bidderRequest, 'refererInfo.page') || deepAccess(window, 'location.href'); - } - - return generalParams -} diff --git a/modules/stroeerCoreBidAdapter.js b/modules/stroeerCoreBidAdapter.js index e67941ed3a1..5a492a8e5e0 100644 --- a/modules/stroeerCoreBidAdapter.js +++ b/modules/stroeerCoreBidAdapter.js @@ -52,7 +52,6 @@ export const spec = { const basePayload = { id: generateUUID(), ref: refererInfo.ref, - ssl: isSecureWindow(), mpa: isMainPageAccessible(), timeout: bidderRequest.timeout - (Date.now() - bidderRequest.auctionStart), url: refererInfo.page, @@ -76,7 +75,7 @@ export const spec = { }; } - const ORTB2_KEYS = ['regs.ext.dsa', 'device.ext.cdep']; + const ORTB2_KEYS = ['regs.ext.dsa', 'device.ext.cdep', 'site.ext']; ORTB2_KEYS.forEach(key => { const value = deepAccess(bidderRequest.ortb2, key); if (value !== undefined) { @@ -117,7 +116,8 @@ export const spec = { creativeId: '', meta: { advertiserDomains: bidResponse.adomain, - dsa: bidResponse.dsa + dsa: bidResponse.dsa, + campaignType: bidResponse.campaignType, }, mediaType, }; @@ -147,8 +147,6 @@ export const spec = { } }; -const isSecureWindow = () => getWindowSelf().location.protocol === 'https:'; - const isMainPageAccessible = () => { try { return !!getWindowTop().location.href; @@ -219,6 +217,7 @@ const mapToPayloadBaseBid = (bidRequest) => ({ bid: bidRequest.bidId, sid: bidRequest.params.sid, viz: elementInView(bidRequest.adUnitCode), + sfp: bidRequest.params.sfp, }); const mapToPayloadBannerBid = (bidRequest) => { @@ -254,14 +253,14 @@ const createFloorPriceObject = (mediaType, sizes, bidRequest) => { currency: 'EUR', mediaType: mediaType, size: '*' - }); + }) || {}; const sizeFloors = sizes.map(size => { const floor = bidRequest.getFloor({ currency: 'EUR', mediaType: mediaType, size: [size[0], size[1]] - }); + }) || {}; return {...floor, size}; }); diff --git a/modules/stvBidAdapter.js b/modules/stvBidAdapter.js index 5cffc5853b5..ef8b815b5f9 100644 --- a/modules/stvBidAdapter.js +++ b/modules/stvBidAdapter.js @@ -1,7 +1,16 @@ -import {deepAccess} from '../src/utils.js'; +import {deepAccess, logMessage} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {includes} from '../src/polyfill.js'; +import { + handleSyncUrls, + isBannerRequest, + isVideoRequest, + convertMediaInfoForRequest, + getMediaTypesInfo, + getBidFloor, + interpretResponse +} from '../libraries/dspxUtils/bidderUtils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -121,88 +130,21 @@ export const spec = { return { method: 'GET', url: endpoint, - data: objectToQueryString(payload), + data: stvObjectToQueryString(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: 60, - meta: { - advertiserDomains: response.adomain || [] - } - }; - if (response.vastXml) { - bidResponse.vastXml = response.vastXml; - bidResponse.mediaType = 'video'; - } else { - bidResponse.ad = response.adTag; - } - - bidResponses.push(bidResponse); - } - return bidResponses; + logMessage('STV: serverResponse', serverResponse); + logMessage('STV: bidRequest', bidRequest); + return interpretResponse(serverResponse, bidRequest, (bidRequest, response) => null); // we don't use any renderer }, 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 !== undefined && - serverResponses[0].body.userSync !== undefined && serverResponses[0].body.userSync.iframeUrl !== undefined && - serverResponses[0].body.userSync.iframeUrl.length > 0) { - 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; + return handleSyncUrls(syncOptions, serverResponses, gdprConsent); } } -function appendToUrl(url, what) { - if (!what) { - return url; - } - return url + (url.indexOf('?') !== -1 ? '&' : '?') + what; -} - -function objectToQueryString(obj, prefix) { +function stvObjectToQueryString(obj, prefix) { let str = []; let p; for (p in obj) { @@ -210,7 +152,7 @@ function objectToQueryString(obj, prefix) { let k = prefix ? prefix + '[' + p + ']' : p; let v = obj[p]; str.push((v !== null && typeof v === 'object') - ? objectToQueryString(v, k) + ? stvObjectToQueryString(v, k) : (k == 'schain' || k == 'uids' ? k + '=' + v : encodeURIComponent(k) + '=' + encodeURIComponent(v))); } } @@ -291,129 +233,4 @@ function serializeUids(bidRequest) { return uids.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' || !!deepAccess(bid, 'mediaTypes.banner') || !isVideoRequest(bid); -} - -/** - * Check if it's a video bid request - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {boolean} True if it's a video bid - */ -function isVideoRequest(bid) { - return bid.mediaType === 'video' || !!deepAccess(bid, 'mediaTypes.video'); -} - -/** - * Get video sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getVideoSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.video.playerSize') || bid.sizes); -} - -/** - * Get banner sizes - * - * @param {BidRequest} bid - Bid request generated from ad slots - * @returns {object} True if it's a video bid - */ -function getBannerSizes(bid) { - return parseSizes(deepAccess(bid, 'mediaTypes.banner.sizes') || bid.sizes); -} - -/** - * Parse size - * @param sizes - * @returns {width: number, h: height} - */ -function parseSize(size) { - let sizeObj = {} - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - return sizeObj; -} - -/** - * Parse sizes - * @param sizes - * @returns {{width: number , height: number }[]} - */ -function parseSizes(sizes) { - if (Array.isArray(sizes[0])) { // is there several sizes ? (ie. [[728,90],[200,300]]) - return sizes.map(size => parseSize(size)); - } - return [parseSize(sizes)]; // or a single one ? (ie. [728,90]) -} - -/** - * Get MediaInfo object for server request - * - * @param mediaTypesInfo - * @returns {*} - */ -function convertMediaInfoForRequest(mediaTypesInfo) { - let requestData = {}; - Object.keys(mediaTypesInfo).forEach(mediaType => { - requestData[mediaType] = mediaTypesInfo[mediaType].map(size => { - return size.width + 'x' + size.height; - }).join(','); - }); - return requestData; -} - -/** - * Get Bid Floor - * @param bid - * @returns {number|*} - */ -function getBidFloor(bid) { - if (typeof bid.getFloor !== 'function') { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'EUR', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 - } -} - -/** - * Get media types info - * - * @param bid - */ -function getMediaTypesInfo(bid) { - let mediaTypesInfo = {}; - - if (bid.mediaTypes) { - Object.keys(bid.mediaTypes).forEach(mediaType => { - if (mediaType === BANNER) { - mediaTypesInfo[mediaType] = getBannerSizes(bid); - } - if (mediaType === VIDEO) { - mediaTypesInfo[mediaType] = getVideoSizes(bid); - } - }); - } else { - mediaTypesInfo[BANNER] = getBannerSizes(bid); - } - return mediaTypesInfo; -} - registerBidder(spec); diff --git a/modules/symitriAnalyticsAdapter.js b/modules/symitriAnalyticsAdapter.js new file mode 100644 index 00000000000..89dc27886e3 --- /dev/null +++ b/modules/symitriAnalyticsAdapter.js @@ -0,0 +1,61 @@ +import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; +import adapterManager from '../src/adapterManager.js'; +import { EVENTS } from '../src/constants.js'; +import { logMessage } from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; + +const analyticsType = 'endpoint'; +const url = 'https://ProdSymPrebidEventhub1.servicebus.windows.net/prebid-said-1/messages'; + +const { BID_WON } = EVENTS; + +let initOptions; + +let symitriAnalytics = Object.assign(adapter({url, analyticsType}), { + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + logMessage('##### symitriAnalytics :: Event Triggered : ' + eventType); + sendEvent(args); + break; + default: + logMessage('##### symitriAnalytics :: Event Triggered : ' + eventType); + break; + } + } +}); + +function sendEvent(payload) { + try { + if (initOptions.apiAuthToken) { + const body = JSON.stringify(payload); + logMessage('##### symitriAnalytics :: sendEvent ', payload); + let cb = { + success: () => { + logMessage('##### symitriAnalytics :: Bid Reported Successfully'); + }, + error: (request, error) => { + logMessage('##### symitriAnalytics :: Bid Report Failed' + error); + } + }; + + ajax(url, cb, body, { + method: 'POST', + customHeaders: {'Content-Type': 'application/atom+xml;type=entry;charset=utf-8', 'Authorization': initOptions.apiAuthToken} + }); + } + } catch (err) { logMessage('##### symitriAnalytics :: error' + err) } +} + +symitriAnalytics.originEnableAnalytics = symitriAnalytics.enableAnalytics; +symitriAnalytics.enableAnalytics = function (config) { + initOptions = config.options; + symitriAnalytics.originEnableAnalytics(config); +}; + +adapterManager.registerAnalyticsAdapter({ + adapter: symitriAnalytics, + code: 'symitri' +}); + +export default symitriAnalytics; diff --git a/modules/symitriAnalyticsAdapter.md b/modules/symitriAnalyticsAdapter.md new file mode 100644 index 00000000000..d7da72ae166 --- /dev/null +++ b/modules/symitriAnalyticsAdapter.md @@ -0,0 +1,35 @@ +### Overview + + Symitri Analytics Adapter. + +### Integration + + 1) Build the symitriAnalyticsAdapter module into the Prebid.js package with: + + ``` + gulp build --modules=symitriAnalyticsAdapter,... + ``` + + 2) Use `enableAnalytics` to instruct Prebid.js to initilaize the symitriAnalyticsAdapter module, as specified below. + +### Configuration + +``` + pbjs.enableAnalytics({ + provider: 'symitri', + options: { + 'apiAuthToken': '' + } + }); + ``` + +Please reach out to your Symitri account representative(Prebid@symitri.com) to get provisioned on the DAP platform. + + +### Testing +To view an example of available segments returned by dap: +``` +‘gulp serve --modules=rtdModule,symitriDapRtdProvider,symitriAnalyticsAdapter,appnexusBidAdapter,sovrnBidAdapter’ +``` +and then point your browser at: +"http://localhost:9999/integrationExamples/gpt/symitridap_segments_example.html" diff --git a/modules/symitriDapRtdProvider.js b/modules/symitriDapRtdProvider.js index 70e27441989..c9c68c25e79 100644 --- a/modules/symitriDapRtdProvider.js +++ b/modules/symitriDapRtdProvider.js @@ -5,18 +5,12 @@ * @module modules/symitriDapRtdProvider * @requires module:modules/realTimeData */ -import { ajax } from '../src/ajax.js'; -import { getStorageManager } from '../src/storageManager.js'; -import { submodule } from '../src/hook.js'; -import { - isPlainObject, - mergeDeep, - logMessage, - logInfo, - logError, -} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; +import {getStorageManager} from '../src/storageManager.js'; +import {submodule} from '../src/hook.js'; +import {isPlainObject, mergeDeep, logMessage, logInfo, logError} from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; -import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; /** * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule @@ -32,12 +26,9 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { const DAP_SS_ID = 'dap_ss_id'; const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds const DAP_MAX_RETRY_TOKENIZE = 1; - const DAP_CLIENT_ENTROPY = 'dap_client_entropy'; + const DAP_CLIENT_ENTROPY = 'dap_client_entropy' - const storage = getStorageManager({ - moduleType: MODULE_TYPE_RTD, - moduleName: SUBMODULE_NAME, - }); + const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); let dapRetryTokenize = 0; /** @@ -78,61 +69,28 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * @param {Object} userConsent */ function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - let entropyDict = JSON.parse( - storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY) - ); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); // Attempt to load entroy script if no entropy object exist and entropy config settings are present. // Else - if ( - !entropyDict && - rtdConfig && - rtdConfig.params && - dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl) - ) { + if (!entropyDict && rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { let loadScriptPromise = new Promise((resolve, reject) => { - if ( - rtdConfig && - rtdConfig.params && - rtdConfig.params.dapEntropyTimeout && - Number.isInteger(rtdConfig.params.dapEntropyTimeout) - ) { - setTimeout( - reject, - rtdConfig.params.dapEntropyTimeout, - Error('DapEntropy script could not be loaded') - ); + if (rtdConfig && rtdConfig.params && rtdConfig.params.dapEntropyTimeout && Number.isInteger(rtdConfig.params.dapEntropyTimeout)) { + setTimeout(reject, rtdConfig.params.dapEntropyTimeout, Error('DapEntropy script could not be loaded')); } - if ( - entropyDict && - entropyDict.expires_at > Math.round(Date.now() / 1000.0) - ) { + if (entropyDict && entropyDict.expires_at > Math.round(Date.now() / 1000.0)) { logMessage('Using cached entropy'); resolve(); } else { if (typeof window.dapCalculateEntropy === 'function') { window.dapCalculateEntropy(resolve, reject); } else { - if ( - rtdConfig && - rtdConfig.params && - dapUtils.isValidHttpsUrl( - rtdConfig.params.dapEntropyUrl - ) - ) { - loadExternalScript( - rtdConfig.params.dapEntropyUrl, - MODULE_CODE, - () => { - dapUtils.dapGetEntropy(resolve, reject); - } - ); + if (rtdConfig && rtdConfig.params && dapUtils.isValidHttpsUrl(rtdConfig.params.dapEntropyUrl)) { + loadExternalScript(rtdConfig.params.dapEntropyUrl, MODULE_TYPE_RTD, MODULE_CODE, () => { + dapUtils.dapGetEntropy(resolve, reject) + }); } else { - reject( - Error( - 'Please check if dapEntropyUrl is specified and is valid under config.params' - ) - ); + reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); } } } @@ -140,18 +98,10 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { loadScriptPromise .catch((error) => { - logError( - 'Entropy could not be calculated due to: ', - error.message - ); + logError('Entropy could not be calculated due to: ', error.message); }) .finally(() => { - generateRealTimeData( - bidConfig, - onDone, - rtdConfig, - userConsent - ); + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); }); } else { logMessage('No dapEntropyUrl is specified.'); @@ -166,22 +116,15 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { dapRetryTokenize = 0; var jsonData = null; if (rtdConfig && isPlainObject(rtdConfig.params)) { - if (rtdConfig.params.segtax == 504) { - let encMembership = - dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (rtdConfig.params.segtax == 710) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); if (encMembership) { - jsonData = dapUtils.dapGetEncryptedRtdObj( - encMembership, - rtdConfig.params.segtax - ); + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) } } else { let membership = dapUtils.dapGetMembershipFromLocalStorage(); if (membership) { - jsonData = dapUtils.dapGetRtdObj( - membership, - rtdConfig.params.segtax - ); + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) } } } @@ -194,14 +137,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { } } // Calling setTimeout to release the main thread so that the bid request could be sent. - setTimeout( - dapUtils.callDapAPIs, - 0, - bidConfig, - onDone, - rtdConfig, - userConsent - ); + setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); } /** @@ -218,10 +154,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { } function onBidResponse(bidResponse, config, userConsent) { - if ( - bidResponse.dealId && - typeof bidResponse.dealId != typeof undefined - ) { + if (bidResponse.dealId && typeof (bidResponse.dealId) != typeof (undefined)) { let membership = dapUtils.dapGetMembershipFromLocalStorage(); // Get Membership details from Local Storage let deals = membership.deals; // Get list of Deals the user is mapped to deals.forEach((deal) => { @@ -259,68 +192,47 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { name: SUBMODULE_NAME, getBidRequestData: getRealTimeData, onBidResponseEvent: onBidResponse, - init: init, + init: init }; submodule(MODULE_NAME, rtdSubmodule); const dapUtils = { - callDapAPIs: function (bidConfig, onDone, rtdConfig, userConsent) { + + callDapAPIs: function(bidConfig, onDone, rtdConfig, userConsent) { if (rtdConfig && isPlainObject(rtdConfig.params)) { let config = { api_hostname: rtdConfig.params.apiHostname, api_version: rtdConfig.params.apiVersion, domain: rtdConfig.params.domain, segtax: rtdConfig.params.segtax, - identity: { - type: rtdConfig.params.identityType, - value: rtdConfig.params.identityValue, - }, + identity: {type: rtdConfig.params.identityType, value: rtdConfig.params.identityValue}, }; let refreshMembership = true; let token = dapUtils.dapGetTokenFromLocalStorage(); const ortb2 = bidConfig.ortb2Fragments.global; logMessage('token is: ', token); - if (token !== null) { - // If token is not null then check the membership in storage and add the RTD object - if (config.segtax == 504) { - // Follow the encrypted membership path - dapUtils.dapRefreshEncryptedMembership( - ortb2, - config, - token, - onDone - ); // Get the encrypted membership from server + if (token !== null) { // If token is not null then check the membership in storage and add the RTD object + if (config.segtax == 710) { // Follow the encrypted membership path + dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone) // Get the encrypted membership from server refreshMembership = false; } else { - dapUtils.dapRefreshMembership( - ortb2, - config, - token, - onDone - ); // Get the membership from server + dapUtils.dapRefreshMembership(ortb2, config, token, onDone) // Get the membership from server refreshMembership = false; } } - dapUtils.dapRefreshToken( - ortb2, - config, - refreshMembership, - onDone - ); // Refresh Token and membership in all the cases + dapUtils.dapRefreshToken(ortb2, config, refreshMembership, onDone) // Refresh Token and membership in all the cases } }, - dapGetEntropy: function (resolve, reject) { + dapGetEntropy: function(resolve, reject) { if (typeof window.dapCalculateEntropy === 'function') { window.dapCalculateEntropy(resolve, reject); } else { - reject( - Error('window.dapCalculateEntropy function is not defined') - ); + reject(Error('window.dapCalculateEntropy function is not defined')) } }, - dapGetTokenFromLocalStorage: function (ttl) { + dapGetTokenFromLocalStorage: function(ttl) { let now = Math.round(Date.now() / 1000.0); // in seconds let token = null; let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); @@ -332,149 +244,88 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return token; }, - dapRefreshToken: function (ortb2, config, refreshMembership, onDone) { + dapRefreshToken: function(ortb2, config, refreshMembership, onDone) { dapUtils.dapLog('Token missing or expired, fetching a new one...'); // Trigger a refresh let now = Math.round(Date.now() / 1000.0); // in seconds - let item = {}; - let configAsync = { ...config }; - dapUtils.dapTokenize( - configAsync, - config.identity, - onDone, - function (token, status, xhr, onDone) { + let item = {} + let configAsync = {...config}; + dapUtils.dapTokenize(configAsync, config.identity, onDone, + function(token, status, xhr, onDone) { item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; let exp = dapUtils.dapExtractExpiryFromToken(token); if (typeof exp == 'number') { item.expires_at = exp - 10; } item.token = token; - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(item) - ); - dapUtils.dapLog( - 'Successfully updated and stored token; expires at ' + - item.expires_at - ); - let dapSSID = xhr.getResponseHeader( - headerPrefix + '-DAP-SS-ID' - ); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored token; expires at ' + item.expires_at); + let dapSSID = xhr.getResponseHeader(headerPrefix + '-DAP-SS-ID'); if (dapSSID) { - storage.setDataInLocalStorage( - DAP_SS_ID, - JSON.stringify(dapSSID) - ); + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); } - let deviceId100 = xhr.getResponseHeader( - headerPrefix + '-DAP-100' - ); + let deviceId100 = xhr.getResponseHeader(headerPrefix + '-DAP-100'); if (deviceId100 != null) { - storage.setDataInLocalStorage( - 'dap_deviceId100', - deviceId100 - ); - dapUtils.dapLog( - 'Successfully stored DAP 100 Device ID: ' + - deviceId100 - ); + storage.setDataInLocalStorage('dap_deviceId100', deviceId100); + dapUtils.dapLog('Successfully stored DAP 100 Device ID: ' + deviceId100); } if (refreshMembership) { - if (config.segtax == 504) { - dapUtils.dapRefreshEncryptedMembership( - ortb2, - config, - token, - onDone - ); + if (config.segtax == 710) { + dapUtils.dapRefreshEncryptedMembership(ortb2, config, token, onDone); } else { - dapUtils.dapRefreshMembership( - ortb2, - config, - token, - onDone - ); + dapUtils.dapRefreshMembership(ortb2, config, token, onDone); } } }, - function (xhr, status, error, onDone) { - logError( - 'ERROR(' + - error + - '): failed to retrieve token! ' + - status - ); - onDone(); + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve token! ' + status); + onDone() } ); }, - dapGetMembershipFromLocalStorage: function () { + dapGetMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds let membership = null; - let item = JSON.parse( - storage.getDataFromLocalStorage(DAP_MEMBERSHIP) - ); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); if (item) { if (now < item.expires_at) { membership = { said: item.said, cohorts: item.cohorts, deals: item.deals, - attributes: null, + attributes: null }; } } return membership; }, - dapRefreshMembership: function (ortb2, config, token, onDone) { + dapRefreshMembership: function(ortb2, config, token, onDone) { let now = Math.round(Date.now() / 1000.0); // in seconds - let item = {}; - let configAsync = { ...config }; - dapUtils.dapMembership( - configAsync, - token, - onDone, - function (membership, status, xhr, onDone) { + let item = {} + let configAsync = {...config}; + dapUtils.dapMembership(configAsync, token, onDone, + function(membership, status, xhr, onDone) { item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken( - membership.said - ); + let exp = dapUtils.dapExtractExpiryFromToken(membership.said) if (typeof exp == 'number') { item.expires_at = exp - 10; } item.said = membership.said; item.cohorts = membership.cohorts; item.deals = membership.deals ? membership.deals : []; - storage.setDataInLocalStorage( - DAP_MEMBERSHIP, - JSON.stringify(item) - ); - dapUtils.dapLog( - 'Successfully updated and stored membership:' - ); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored membership:'); dapUtils.dapLog(item); - let data = dapUtils.dapGetRtdObj(item, config.segtax); - dapUtils.checkAndAddRealtimeData( - ortb2, - data, - config.segtax - ); + let data = dapUtils.dapGetRtdObj(item, config.segtax) + dapUtils.checkAndAddRealtimeData(ortb2, data, config.segtax); onDone(); }, - function (xhr, status, error, onDone) { - logError( - 'ERROR(' + - error + - '): failed to retrieve membership! ' + - status - ); - if ( - status == 403 && - dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE - ) { + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { dapRetryTokenize++; dapUtils.dapRefreshToken(ortb2, config, true, onDone); } else { @@ -484,68 +335,43 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { ); }, - dapGetEncryptedMembershipFromLocalStorage: function () { + dapGetEncryptedMembershipFromLocalStorage: function() { let now = Math.round(Date.now() / 1000.0); // in seconds let encMembership = null; - let item = JSON.parse( - storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP) - ); + let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); if (item) { if (now < item.expires_at) { encMembership = { - encryptedSegments: item.encryptedSegments, + encryptedSegments: item.encryptedSegments }; } } return encMembership; }, - dapRefreshEncryptedMembership: function (ortb2, config, token, onDone) { + dapRefreshEncryptedMembership: function(ortb2, config, token, onDone) { let now = Math.round(Date.now() / 1000.0); // in seconds let item = {}; - let configAsync = { ...config }; - dapUtils.dapEncryptedMembership( - configAsync, - token, - onDone, - function (encToken, status, xhr, onDone) { + let configAsync = {...config}; + dapUtils.dapEncryptedMembership(configAsync, token, onDone, + function(encToken, status, xhr, onDone) { item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; let exp = dapUtils.dapExtractExpiryFromToken(encToken); if (typeof exp == 'number') { item.expires_at = exp - 10; } item.encryptedSegments = encToken; - storage.setDataInLocalStorage( - DAP_ENCRYPTED_MEMBERSHIP, - JSON.stringify(item) - ); - dapUtils.dapLog( - 'Successfully updated and stored encrypted membership:' - ); + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(item)); + dapUtils.dapLog('Successfully updated and stored encrypted membership:'); dapUtils.dapLog(item); - let encData = dapUtils.dapGetEncryptedRtdObj( - item, - config.segtax - ); - dapUtils.checkAndAddRealtimeData( - ortb2, - encData, - config.segtax - ); + let encData = dapUtils.dapGetEncryptedRtdObj(item, config.segtax); + dapUtils.checkAndAddRealtimeData(ortb2, encData, config.segtax); onDone(); }, - function (xhr, status, error, onDone) { - logError( - 'ERROR(' + - error + - '): failed to retrieve encrypted membership! ' + - status - ); - if ( - status == 403 && - dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE - ) { + function(xhr, status, error, onDone) { + logError('ERROR(' + error + '): failed to retrieve encrypted membership! ' + status); + if (status == 403 && dapRetryTokenize < DAP_MAX_RETRY_TOKENIZE) { dapRetryTokenize++; dapUtils.dapRefreshToken(ortb2, config, true, onDone); } else { @@ -559,17 +385,17 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * DESCRIPTION * Extract expiry value from a token */ - dapExtractExpiryFromToken: function (token) { + dapExtractExpiryFromToken: function(token) { let exp = null; if (token) { const tokenArray = token.split('..'); if (tokenArray && tokenArray.length > 0) { - let decode = atob(tokenArray[0]); + let decode = atob(tokenArray[0]) let header = JSON.parse(decode.replace(/"/g, '"')); exp = header.exp; } } - return exp; + return exp }, /** @@ -578,13 +404,13 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * Convert a DAP membership response to an OpenRTB2 segment object suitable * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapGetRtdObj: function (membership, segtax) { + dapGetRtdObj: function(membership, segtax) { let segment = { name: 'dap.symitri.com', ext: { - segtax: segtax, + 'segtax': segtax }, - segment: [], + segment: [] }; if (membership != null) { for (const i of membership.cohorts) { @@ -595,17 +421,19 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { rtd: { ortb2: { user: { - data: [segment], + data: [ + segment + ] }, site: { ext: { data: { - dapSAID: membership.said, - }, - }, - }, - }, - }, + dapSAID: membership.said + } + } + } + } + } }; return data; }, @@ -616,13 +444,13 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * Convert a DAP membership response to an OpenRTB2 segment object suitable * for insertion into user.data.segment or site.data.segment and add it to the rtd obj. */ - dapGetEncryptedRtdObj: function (encToken, segtax) { + dapGetEncryptedRtdObj: function(encToken, segtax) { let segment = { name: 'dap.symitri.com', ext: { - segtax: segtax, + 'segtax': segtax }, - segment: [], + segment: [] }; if (encToken != null) { segment.segment.push({ id: encToken.encryptedSegments }); @@ -631,20 +459,19 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { rtd: { ortb2: { user: { - data: [segment], - }, - }, - }, + data: [ + segment + ] + } + } + } }; return encData; }, - checkAndAddRealtimeData: function (ortb2, data, segtax) { + checkAndAddRealtimeData: function(ortb2, data, segtax) { if (data.rtd) { - if ( - segtax == 504 && - dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504) - ) { + if (segtax == 710 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 710)) { logMessage('DEBUG(handleInit): rtb Object already added'); } else { addRealTimeData(ortb2, data.rtd); @@ -653,25 +480,22 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { } }, - checkIfSegmentsAlreadyExist: function (ortb2, rtd, segtax) { - let segmentsExist = false; + checkIfSegmentsAlreadyExist: function(ortb2, rtd, segtax) { + let segmentsExist = false if (ortb2.user && ortb2.user.data && ortb2.user.data.length > 0) { for (let i = 0; i < ortb2.user.data.length; i++) { - let element = ortb2.user.data[i]; + let element = ortb2.user.data[i] if (element.ext && element.ext.segtax == segtax) { - segmentsExist = true; - logMessage( - 'DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', - ortb2.user.data - ); + segmentsExist = true + logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); break; } } } - return segmentsExist; + return segmentsExist }, - dapLog: function (args) { + dapLog: function(args) { let css = ''; css += 'display: inline-block;'; css += 'color: #fff;'; @@ -682,7 +506,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { logInfo('%cDAP Client', css, args); }, - isValidHttpsUrl: function (urlString) { + isValidHttpsUrl: function(urlString) { let url; try { url = new URL(urlString); @@ -692,29 +516,15 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { return url.protocol === 'https:'; }, - checkConsent: function (userConsent) { + checkConsent: function(userConsent) { let consent = true; - if ( - userConsent && - userConsent.gdpr && - userConsent.gdpr.gdprApplies - ) { + if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { const gdpr = userConsent.gdpr; - const hasGdpr = - gdpr && - typeof gdpr.gdprApplies === 'boolean' && - gdpr.gdprApplies - ? 1 - : 0; + const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; const gdprConsentString = hasGdpr ? gdpr.consentString : ''; - if ( - hasGdpr && - (!gdprConsentString || gdprConsentString === '') - ) { - logError( - 'symitriDapRtd submodule requires consent string to call API' - ); + if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { + logError('symitriDapRtd submodule requires consent string to call API'); consent = false; } } else if (userConsent && userConsent.usp) { @@ -726,86 +536,53 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { }, /******************************************************************************* - * - * V2 (And Beyond) API - * - ******************************************************************************/ + * + * V2 (And Beyond) API + * + ******************************************************************************/ - dapValidationHelper: function (config, onDone, token, onError) { + dapValidationHelper: function(config, onDone, token, onError) { if (onError == null) { - onError = function (xhr, status, error, onDone) {}; + onError = function(xhr, status, error, onDone) {}; } - if (config == null || typeof config == typeof undefined) { + if (config == null || typeof (config) == typeof (undefined)) { onError(null, 'Invalid config object', 'ClientError', onDone); - return [config, true]; + return [ config, true ]; } - if ( - !('api_version' in config) || - (typeof config.api_version == 'string' && - config.api_version.length == 0) - ) { + if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { config.api_version = 'x1'; } - if (typeof config.api_version != 'string') { - onError( - null, - "Invalid api_version: must be a string like 'x1', etc.", - 'ClientError', - onDone - ); - return [config, true]; + if (typeof (config.api_version) != 'string') { + onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); + return [ config, true ]; } - if ( - !('api_hostname' in config) || - typeof config.api_hostname != 'string' || - config.api_hostname.length == 0 - ) { - onError( - null, - 'Invalid api_hostname: must be a non-empty string', - 'ClientError', - onDone - ); - return [config, true]; + if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { + onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); + return [ config, true ]; } if (token) { - if (typeof token != 'string') { - onError( - null, - 'Invalid token: must be a non-null string', - 'ClientError', - onDone - ); - return [config, true]; + if (typeof (token) != 'string') { + onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); + return [ config, true ]; } } - return [config, false]; + return [ config, false ]; }, - addIdentifier: async function (identity, apiParams) { - if ( - window.crypto && - window.crypto.subtle && - typeof identity.value != typeof undefined && - identity.value.trim() !== '' - ) { - const hashBuffer = await window.crypto.subtle.digest( - 'SHA-256', - new TextEncoder().encode(identity.value) - ); + addIdentifier: async function(identity, apiParams) { + if (window.crypto && window.crypto.subtle && typeof (identity.value) != typeof (undefined) && identity.value.trim() !== '') { + const hashBuffer = await window.crypto.subtle.digest('SHA-256', new TextEncoder().encode(identity.value)); const hashArray = Array.from(new Uint8Array(hashBuffer)); - const hashHex = hashArray - .map((b) => b.toString(16).padStart(2, '0')) - .join(''); + const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); apiParams.identity = hashHex.toUpperCase(); } - return apiParams; + return apiParams }, /** @@ -847,117 +624,55 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * function( response, status, xhr } { token = response; }, * function( xhr, status, error ) { ; } // handle error */ - dapTokenize: function ( - config, - identity, - onDone, - onSuccess = null, - onError = null - ) { + dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { let hasTokenizeError; - [config, hasTokenizeError] = this.dapValidationHelper( - config, - onDone, - null, - onError - ); - if (hasTokenizeError) { - return; - } + [ config, hasTokenizeError ] = this.dapValidationHelper(config, onDone, null, onError); + if (hasTokenizeError) { return; } - if (typeof config.domain != 'string') { - onError( - null, - 'Invalid config.domain: must be a string', - 'ClientError', - onDone - ); + if (typeof (config.domain) != 'string') { + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } if (config.domain.length <= 0) { - onError( - null, - 'Invalid config.domain: must have non-zero length', - 'ClientError', - onDone - ); + onError(null, 'Invalid config.domain: must have non-zero length', 'ClientError', onDone); return; } - if (identity == null || typeof identity == typeof undefined) { + if (identity == null || typeof (identity) == typeof (undefined)) { onError(null, 'Invalid identity object', 'ClientError', onDone); return; } - if ( - !('type' in identity) || - typeof identity.type != 'string' || - identity.type.length <= 0 - ) { - onError( - null, - "Identity must contain a valid 'type' field", - 'ClientError', - onDone - ); + if (!('type' in identity) || typeof (identity.type) != 'string' || identity.type.length <= 0) { + onError(null, "Identity must contain a valid 'type' field", 'ClientError', onDone); return; } let apiParams = { - type: identity.type.toLowerCase(), - identity: identity.value, + 'type': identity.type.toLowerCase(), + 'identity': identity.value }; if (identity.type === 'simpleid') { this.addIdentifier(identity, apiParams).then((apiParams) => { - this.callTokenize( - config, - identity, - apiParams, - onDone, - onSuccess, - onError - ); + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); }); } else if (identity.type === 'compositeid') { identity = JSON.stringify(identity); - this.callTokenize( - config, - identity, - apiParams, - onDone, - onSuccess, - onError - ); + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); } else if (identity.type === 'hashedid') { - this.callTokenize( - config, - identity, - apiParams, - onDone, - onSuccess, - onError - ); + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); } else { - this.callTokenize( - config, - identity, - apiParams, - onDone, - onSuccess, - onError - ); + this.callTokenize(config, identity, apiParams, onDone, onSuccess, onError); } }, callTokenize(config, identity, apiParams, onDone, onSuccess, onError) { - if (typeof identity.attributes != typeof undefined) { + if (typeof (identity.attributes) != typeof (undefined)) { apiParams.attributes = identity.attributes; } - let entropyDict = JSON.parse( - storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY) - ); + let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); if (entropyDict && entropyDict.entropy) { apiParams.entropy = entropyDict.entropy; } @@ -969,28 +684,16 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { case 'x1': case 'x1-dev': method = 'POST'; - path = - '/data-activation/' + - config.api_version + - '/domain/' + - config.domain + - '/identity/tokenize'; + path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize'; body = JSON.stringify(apiParams); break; default: - onError( - null, - 'Invalid api_version: ' + config.api_version, - 'ClientError', - onDone - ); + onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); return; } let customHeaders = { 'Content-Type': 'application/json' }; - let dapSSID = JSON.parse( - storage.getDataFromLocalStorage(DAP_SS_ID) - ); + let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); if (dapSSID) { customHeaders[headerPrefix + '-DAP-SS-ID'] = dapSSID; } @@ -1002,21 +705,19 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { switch (config.api_version) { case 'x1': case 'x1-dev': - token = request.getResponseHeader( - headerPrefix + '-DAP-Token' - ); + token = request.getResponseHeader(headerPrefix + '-DAP-Token'); break; } onSuccess(token, request.status, request, onDone); }, error: (request, error) => { onError(request, request.statusText, error, onDone); - }, + } }; ajax(url, cb, body, { method: method, - customHeaders: customHeaders, + customHeaders: customHeaders }); }, @@ -1053,60 +754,35 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * } ); * */ - dapMembership: function ( - config, - token, - onDone, - onSuccess = null, - onError = null - ) { + dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { let hasMembershipError; - [config, hasMembershipError] = this.dapValidationHelper( - config, - onDone, - token, - onError - ); - if (hasMembershipError) { - return; - } + [ config, hasMembershipError ] = this.dapValidationHelper(config, onDone, token, onError); + if (hasMembershipError) { return; } - if (typeof config.domain != 'string') { - onError( - null, - 'Invalid config.domain: must be a string', - 'ClientError', - onDone - ); + if (typeof (config.domain) != 'string') { + onError(null, 'Invalid config.domain: must be a string', 'ClientError', onDone); return; } - let path = - '/data-activation/' + - config.api_version + - '/token/' + - token + - '/membership'; + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership'; let url = 'https://' + config.api_hostname + path; let cb = { success: (response, request) => { - onSuccess( - JSON.parse(response), - request.status, - request, - onDone - ); + onSuccess(JSON.parse(response), request.status, request, onDone); }, error: (error, request) => { onError(request, request.status, error, onDone); - }, + } }; ajax(url, cb, undefined, { method: 'GET', - customHeaders: {}, + customHeaders: {} }); }, @@ -1143,42 +819,23 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { * } ); * */ - dapEncryptedMembership: function ( - config, - token, - onDone, - onSuccess = null, - onError = null - ) { + dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { let hasEncryptedMembershipError; - [config, hasEncryptedMembershipError] = this.dapValidationHelper( - config, - onDone, - token, - onError - ); - if (hasEncryptedMembershipError) { - return; - } + [ config, hasEncryptedMembershipError ] = this.dapValidationHelper(config, onDone, token, onError); + if (hasEncryptedMembershipError) { return; } let cb = { success: (response, request) => { - let encToken = request.getResponseHeader( - headerPrefix + '-DAP-Token' - ); + let encToken = request.getResponseHeader(headerPrefix + '-DAP-Token'); onSuccess(encToken, request.status, request, onDone); }, - error: (error, request) => { - onError(request, request.status, error, onDone); - }, + error: (error, request) => { onError(request, request.status, error, onDone); } }; - let path = - '/data-activation/' + - config.api_version + - '/token/' + - token + - '/membership/encrypt'; + let path = '/data-activation/' + + config.api_version + + '/token/' + token + + '/membership/encrypt'; let url = 'https://' + config.api_hostname + path; @@ -1186,11 +843,11 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { method: 'GET', customHeaders: { 'Content-Type': 'application/json', - Pragma: 'akamai-x-get-extracted-values', - }, + 'Pragma': 'akamai-x-get-extracted-values' + } }); - }, - }; + } + } return { addRealTimeData, @@ -1205,7 +862,7 @@ export function createRtdProvider(moduleName, moduleCode, headerPrefix) { DAP_SS_ID, DAP_DEFAULT_TOKEN_TTL, DAP_MAX_RETRY_TOKENIZE, - DAP_CLIENT_ENTROPY, + DAP_CLIENT_ENTROPY }; } @@ -1222,5 +879,5 @@ export const { DAP_SS_ID, DAP_DEFAULT_TOKEN_TTL, DAP_MAX_RETRY_TOKENIZE, - DAP_CLIENT_ENTROPY, + DAP_CLIENT_ENTROPY } = createRtdProvider('symitriDap', 'symitridap', 'Symitri'); diff --git a/modules/symitriDapRtdProvider.md b/modules/symitriDapRtdProvider.md index 28f6265b761..e3429e24144 100644 --- a/modules/symitriDapRtdProvider.md +++ b/modules/symitriDapRtdProvider.md @@ -47,7 +47,7 @@ pbjs.setConfig({ domain: 'your-domain.com', identityType: 'simpleid'|'compositeid'|'hashedid'|'dap-signature:1.0.0', identityValue: '', - segtax: 501, + segtax: 708, dapEntropyUrl: 'https://sym-dist.symitri.net/dapentropy.js', dapEntropyTimeout: 1500, pixelUrl: '', @@ -80,7 +80,7 @@ Use 'hashedid' to pass in single already hashed id. Use 'compositeid' to pass in } | | identityValue | String | This is optional field to pass user hid. Will be used only if identityType is hid | | -| segtax | Integer | The taxonomy for Symitri | The value should be 501 | +| segtax | Integer | The taxonomy for Symitri | The value should be 708 | | dapEntropyUrl | String | URL to dap entropy script | Optional if the script is directly included on the webpage. Contact your Symitri account rep for more details | | dapEntropyTimeout | Integer | Maximum time allotted for the entropy calculation to happen | | | pixelUrl | String | Pixel URL provided by Symitri which will be triggered when bid matching with Symitri dealid wins and creative gets rendered | | @@ -94,4 +94,4 @@ gulp serve --modules=rtdModule,symitriDapRtdProvider,appnexusBidAdapter,sovrnBid ``` and then point your browser at: -"" \ No newline at end of file +"" diff --git a/modules/taboolaBidAdapter.js b/modules/taboolaBidAdapter.js index 6e6d89dc921..da7559ef144 100644 --- a/modules/taboolaBidAdapter.js +++ b/modules/taboolaBidAdapter.js @@ -3,7 +3,7 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; -import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse} from '../src/utils.js'; +import {deepAccess, deepSetValue, getWindowSelf, replaceAuctionPrice, isArray, safeJSONParse, isPlainObject} from '../src/utils.js'; import {getStorageManager} from '../src/storageManager.js'; import {ajax} from '../src/ajax.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; @@ -64,6 +64,7 @@ export const userData = { return tblaId; } } + return undefined; }, getCookieDataByKey(cookieData, key) { if (!cookieData) { @@ -78,6 +79,7 @@ export const userData = { if (hasLocalStorage() && localStorageIsEnabled()) { return getDataFromLocalStorage(STORAGE_KEY); } + return undefined; }, getFromTRC() { return window.TRC ? window.TRC.user_id : 0; @@ -113,6 +115,9 @@ const converter = ortbConverter({ const bidResponse = buildBidResponse(bid, context); bidResponse.nurl = bid.nurl; bidResponse.ad = replaceAuctionPrice(bid.adm, bid.price); + if (bid.ext && bid.ext.dchain) { + deepSetValue(bidResponse, 'meta.dchain', bid.ext.dchain); + } return bidResponse } }); @@ -201,7 +206,7 @@ export const spec = { if (fledgeAuctionConfigs.length) { return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, }; } return bids; @@ -271,35 +276,38 @@ function getSiteProperties({publisherId}, refererInfo, ortb2) { function fillTaboolaReqData(bidderRequest, bidRequest, data) { const {refererInfo, gdprConsent = {}, uspConsent} = bidderRequest; const site = getSiteProperties(bidRequest.params, refererInfo, bidderRequest.ortb2); - const device = {ua: navigator.userAgent}; - let user = { - buyeruid: userData.getUserId(gdprConsent, uspConsent), - ext: {} - }; - if (bidderRequest && bidderRequest.ortb2 && bidderRequest.ortb2.user) { - user.data = bidderRequest.ortb2.user.data; + deepSetValue(data, 'device.ua', navigator.userAgent); + const extractedUserId = userData.getUserId(gdprConsent, uspConsent); + if (data.user == undefined) { + data.user = { + buyeruid: 0, + ext: {} + } } - const regs = { - coppa: 0, - ext: {} - }; - + if (extractedUserId && extractedUserId !== 0) { + deepSetValue(data, 'user.buyeruid', extractedUserId); + } + if (data.regs?.ext == undefined) { + data.regs = { + ext: {} + } + } + deepSetValue(data, 'regs.coppa', 0); if (gdprConsent.gdprApplies) { - user.ext.consent = bidderRequest.gdprConsent.consentString; - regs.ext.gdpr = 1; + deepSetValue(data, 'user.ext.consent', bidderRequest.gdprConsent.consentString); + deepSetValue(data, 'regs.ext.gdpr', 1); } - if (uspConsent) { - regs.ext.us_privacy = uspConsent; + deepSetValue(data, 'regs.ext.us_privacy', uspConsent); } if (bidderRequest.ortb2?.regs?.gpp) { - regs.ext.gpp = bidderRequest.ortb2.regs.gpp; - regs.ext.gpp_sid = bidderRequest.ortb2.regs.gpp_sid; + deepSetValue(data, 'regs.ext.gpp', bidderRequest.ortb2.regs.gpp); + deepSetValue(data, 'regs.ext.gpp_sid', bidderRequest.ortb2.regs.gpp_sid); } if (config.getConfig('coppa')) { - regs.coppa = 1; + deepSetValue(data, 'regs.coppa', 1); } const ortb2 = bidderRequest.ortb2 || { @@ -308,16 +316,14 @@ function fillTaboolaReqData(bidderRequest, bidRequest, data) { wlang: [] }; + deepSetValue(data, 'source.fd', 1); + data.id = bidderRequest.bidderRequestId; data.site = site; - data.device = device; - data.source = {fd: 1}; data.tmax = (bidderRequest.timeout == undefined) ? undefined : parseInt(bidderRequest.timeout); data.bcat = ortb2.bcat || bidRequest.params.bcat || []; data.badv = ortb2.badv || bidRequest.params.badv || []; data.wlang = ortb2.wlang || bidRequest.params.wlang || []; - data.user = user; - data.regs = regs; deepSetValue(data, 'ext.pageType', ortb2?.ext?.data?.pageType || ortb2?.ext?.data?.section || bidRequest.params.pageType); deepSetValue(data, 'ext.prebid.version', '$prebid.version$'); } @@ -332,7 +338,7 @@ function fillTaboolaImpData(bid, imp) { currency: CURRENCY, size: '*' }); - if (typeof floorInfo === 'object' && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { + if (isPlainObject(floorInfo) && floorInfo.currency === CURRENCY && !isNaN(parseFloat(floorInfo.floor))) { imp.bidfloor = parseFloat(floorInfo.floor); imp.bidfloorcur = CURRENCY; } diff --git a/modules/tagorasBidAdapter.js b/modules/tagorasBidAdapter.js index 0138ba3daf9..6ce54a5a895 100644 --- a/modules/tagorasBidAdapter.js +++ b/modules/tagorasBidAdapter.js @@ -1,333 +1,30 @@ -import {_each, deepAccess, parseSizesInput, parseUrl, uniques, isFn} 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 {config} from '../src/config.js'; +import { + createBuildRequestsFn, + createInterpretResponseFn, + createUserSyncGetter, + isBidRequestValid +} from '../libraries/vidazooUtils/bidderUtils.js'; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'tagoras'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 15; -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.tagoras.io`; } -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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - let {bidFloor, ext} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const cId = extractCID(params); - const pId = extractPID(params); - const subDomain = extractSubDomain(params); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - gpid: gpid, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - - _each(ext, (value, key) => { - dto.data['ext.' + key] = value; - }); - - return dto; -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, false); - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, false); -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - const requests = []; - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - const {bidId} = request.data; - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach(result => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - metaData, - advertiserDomains, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: bidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}` - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.tagoras.io/api/sync/iframe/${params}` - }); - } else if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.tagoras.io/api/sync/image/${params}` - }); - } - return syncs; -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.tagoras.io/api/sync/iframe', + imageSyncUrl: 'https://sync.tagoras.io/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/tapnativeBidAdapter.js b/modules/tapnativeBidAdapter.js new file mode 100644 index 00000000000..a09c746c960 --- /dev/null +++ b/modules/tapnativeBidAdapter.js @@ -0,0 +1,41 @@ +import { + BANNER, + NATIVE +} from '../src/mediaTypes.js'; +import { + registerBidder +} from '../src/adapters/bidderFactory.js'; +import { + getBannerRequest, + getBannerResponse, + getNativeResponse, +} from '../libraries/audUtils/bidderUtils.js'; + +const ENDPOINT = 'https://rtb-east.tapnative.com/hb'; +// Export const spec +export const spec = { + code: 'tapnative', + supportedMediaTypes: [BANNER, NATIVE], + // Determines whether or not the given bid request is valid + isBidRequestValid: function(bidParam) { + return !!(bidParam.params.placement_id); + }, + // Make a server request from the list of BidRequests + buildRequests: function(bidRequests, serverRequest) { + // Get Requests based on media types + return getBannerRequest(bidRequests, serverRequest, ENDPOINT); + }, + // Unpack the response from the server into a list of bids. + interpretResponse: function(serverResponse, serverRequest) { + let bidderResponse = {}; + const mType = JSON.parse(serverRequest.data)[0].MediaType; + if (mType == BANNER) { + bidderResponse = getBannerResponse(serverResponse, BANNER); + } else if (mType == NATIVE) { + bidderResponse = getNativeResponse(serverResponse, serverRequest, NATIVE); + } + return bidderResponse; + } +} + +registerBidder(spec); diff --git a/modules/tapnativeBidAdapter.md b/modules/tapnativeBidAdapter.md new file mode 100644 index 00000000000..2cc39475dcb --- /dev/null +++ b/modules/tapnativeBidAdapter.md @@ -0,0 +1,61 @@ +# Overview + +``` +Module Name: Tapnative Bidder Adapter +Module Type: Bidder Adapter +Maintainer: support@tapnative.com +``` + +# Description + +Tapnative currently supports the BANNER and NATIVE type ads through prebid js + +Module that connects to tapnative's demand sources. + +# Test Request +``` + var adUnits = [ + { + code: 'display-ad', + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + } + bids: [ + { + bidder: 'tapnative', + params: { + placement_id: 111520, // Required parameter + width: 300, // Optional parameter + height: 250, // Optional parameter + bid_floor: 0.5 // Optional parameter + } + } + ] + }, + { + code: 'native-ad-container', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + bids: [ + { + bidder: 'tapnative', + params: { + placement_id: 111519, // Required parameter + bid_floor: 1 // Optional parameter + } + } + ] + } + ]; +``` diff --git a/modules/tappxBidAdapter.js b/modules/tappxBidAdapter.js index e1777f749e5..70d74396ea9 100644 --- a/modules/tappxBidAdapter.js +++ b/modules/tappxBidAdapter.js @@ -6,7 +6,6 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { parseDomain } from '../src/refererDetection.js'; -import { getGlobal } from '../src/prebidGlobal.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -333,11 +332,8 @@ function buildOneRequest(validBidRequests, bidderRequest) { banner.api = api; - let format = {}; - format[0] = {}; - format[0].w = w; - format[0].h = h; - banner.format = format; + const formatArr = bannerMediaType.sizes.map(size => ({w: size[0], h: size[1]})) + banner.format = Object.assign({}, formatArr); imp.banner = banner; } @@ -377,7 +373,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { imp.id = validBidRequests.bidId; imp.tagid = tagid; - imp.secure = 1; + imp.secure = validBidRequests.ortb2Imp?.secure ?? 1; imp.bidfloor = deepAccess(validBidRequests, 'params.bidfloor'); if (isFn(validBidRequests.getFloor)) { @@ -498,7 +494,7 @@ function buildOneRequest(validBidRequests, bidderRequest) { payload.regs = regs; // < Payload - let pbjsv = (getGlobal().version !== null) ? encodeURIComponent(getGlobal().version) : -1; + let pbjsv = 'v' + '$prebid.version$'; return { method: 'POST', diff --git a/modules/targetVideoAdServerVideo.js b/modules/targetVideoAdServerVideo.js new file mode 100644 index 00000000000..335fbcda730 --- /dev/null +++ b/modules/targetVideoAdServerVideo.js @@ -0,0 +1,96 @@ +import { registerVideoSupport } from '../src/adServerManager.js'; +import { targeting } from '../src/targeting.js'; +import { buildUrl, isEmpty, isPlainObject, logError, parseUrl } from '../src/utils.js'; + +/** + * Merge all the bid data and publisher-supplied options into a single URL, and then return it. + * @param {Object} options - The options object. + * @param {Object} options.params - params property. + * @param {string} options.params.iu - Required iu property. + * @param {Object} options.adUnit - adUnit property. + * @param {Object} [options.bid] - Optional bid property. + * @returns {string|undefined} The generated URL (if neither bid nor adUnit property is provided the generated string is undefined). + */ +export function buildVideoUrl(options) { + if (!options.params || !options.params?.iu) { + logError('buildVideoUrl: Missing required properties.'); + return; + } + + if (!isPlainObject(options.adUnit) && !isPlainObject(options.bid)) { + logError('buildVideoUrl: Requires either \'adUnit\' or \'bid\' options value'); + return; + } + + const isURL = /^(https?:\/\/)/i; + const defaultParameters = { + autoplay: '[autoplay]', + mute: '[vpmute]', + page_url: '[page_url]', + cachebuster: '[timestamp]', + gdpr_consent: '[consent]', + } + + const adUnit = options.adUnit; + const bid = options.bid || targeting.getWinningBids(adUnit.code)[0]; + const allTargetingData = getAllTargetingData(options); + const custParams = options.params.cust_params; + let iu = options.params.iu; + + if (isURL.test(iu)) { + const urlComponents = parseUrl(iu, {noDecodeWholeURL: true}); + + for (const [key, value] of Object.entries({...allTargetingData, ...bid.adserverTargeting, ...defaultParameters})) { + if (!urlComponents.search.hasOwnProperty(key)) { + urlComponents.search[key] = value; + } + } + + if (urlComponents.search.cust_params) { + for (const [key, value] of Object.entries(custParams)) { + if (!urlComponents.search.cust_params.includes(key)) { + urlComponents.search.cust_params += '%26' + key + '%3D' + value; + } + } + } + + if (!isEmpty(custParams) && !urlComponents.search.cust_params) { + urlComponents.search.cust_params = Object.entries(custParams).map(([key, value]) => key + '%3D' + value).join('%26'); + } + + return buildUrl(urlComponents); + } + + const search = { + iu, + ...defaultParameters, + ...allTargetingData, + ...bid.adserverTargeting, + }; + + if (!isEmpty(custParams)) { + search.cust_params = Object.entries(custParams).map(([key, value]) => key + '%3D' + value).join('%26'); + } + + return buildUrl({ + protocol: 'https', + host: 'vid.tvserve.io', + pathname: '/ads/bid', + search + }); +} + +function getAllTargetingData(options) { + let allTargetingData = {}; + const adUnit = options && options.adUnit; + if (adUnit) { + let allTargeting = targeting.getAllTargeting(adUnit.code); + allTargetingData = allTargeting ? allTargeting[adUnit.code] : {}; + } + + return allTargetingData; +} + +registerVideoSupport('targetVideo', { + buildVideoUrl, +}); diff --git a/modules/targetVideoAdServerVideo.md b/modules/targetVideoAdServerVideo.md new file mode 100644 index 00000000000..89592b32478 --- /dev/null +++ b/modules/targetVideoAdServerVideo.md @@ -0,0 +1,26 @@ +# Overview + +``` +Module Name: Target Video Ad Server Video +Module Type: Ad Server Video +Maintainer: danijel.ristic@target-video.com +``` + +# Description + +Ad Server Video for target-video.com. Contact danijel.ristic@target-video.com for information. + +# Integration + +The helper function takes the form: + + pbjs.adServers.targetVideo.buildVideoUrl(options) + +Where: + +* **`options`:** configuration object: + * **`params`:** + * **`iu`:** required property used to construct valid VAST tag URL + * **`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, similar to the value returned by `pbjs.adServers.dfp.buildVideoUrl(...)` diff --git a/modules/targetVideoBidAdapter.js b/modules/targetVideoBidAdapter.js index 282f322c36a..b01e3dddab3 100644 --- a/modules/targetVideoBidAdapter.js +++ b/modules/targetVideoBidAdapter.js @@ -1,24 +1,19 @@ -import {find} from '../src/polyfill.js'; -import {getBidRequest} from '../src/utils.js'; +import {_each, getDefinedParams, parseGPTSingleSizeArrayToRtbSize} from '../src/utils.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {formatRequest, getRtbBid, getSiteObj, getSyncResponse, videoBid, bannerBid, createVideoTag} from '../libraries/targetVideoUtils/bidderUtils.js'; +import {SOURCE, GVLID, BIDDER_CODE, VIDEO_PARAMS, BANNER_ENDPOINT_URL, VIDEO_ENDPOINT_URL, MARGIN, TIME_TO_LIVE} from '../libraries/targetVideoUtils/constants.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid */ -const SOURCE = 'pbjs'; -const BIDDER_CODE = 'targetVideo'; -const ENDPOINT_URL = 'https://ib.adnxs.com/ut/v3/prebid'; -const MARGIN = 1.35; -const GVLID = 786; - export const spec = { code: BIDDER_CODE, gvlid: GVLID, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /** * Determines whether or not the given bid request is valid. @@ -37,35 +32,114 @@ export const spec = { * @return ServerRequest Info describing the request to the server. */ buildRequests: function(bidRequests, bidderRequest) { - const tags = bidRequests.map(createVideoTag); - const schain = bidRequests[0].schain; - const payload = { - tags: tags, - sdk: { - source: SOURCE, - version: '$prebid.version$' - }, - schain: schain + const requests = []; + const sdk = { + source: SOURCE, + version: '$prebid.version$' }; - if (bidderRequest && bidderRequest.gdprConsent) { - payload.gdpr_consent = { - consent_string: bidderRequest.gdprConsent.consentString, - consent_required: bidderRequest.gdprConsent.gdprApplies - }; - - if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { - let ac = bidderRequest.gdprConsent.addtlConsent; - let acStr = ac.substring(ac.indexOf('~') + 1); - payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + for (let {params, bidId, sizes, mediaTypes} of bidRequests) { + for (const mediaType in mediaTypes) { + switch (mediaType) { + case VIDEO: { + const video = mediaTypes[VIDEO]; + const placementId = params.placementId; + const site = getSiteObj(); + + if (sizes && !Array.isArray(sizes[0])) sizes = [sizes]; + + const payload = { + sdk, + id: bidderRequest.bidderRequestId, + site, + imp: [] + } + + const imp = { + ext: { + prebid: { + storedrequest: { id: placementId } + } + }, + video: getDefinedParams(video, VIDEO_PARAMS) + } + + if (video.playerSize) { + imp.video = Object.assign( + imp.video, parseGPTSingleSizeArrayToRtbSize(video.playerSize[0]) || {} + ); + } else if (video.w && video.h) { + imp.video.w = video.w; + imp.video.h = video.h; + } + + payload.imp.push(imp); + + const gdprConsent = bidderRequest && bidderRequest.gdprConsent; + const uspConsent = bidderRequest && bidderRequest.uspConsent; + + if (gdprConsent || uspConsent) { + payload.regs = { ext: {} }; + + if (uspConsent) { + payload.regs.ext.us_privacy = uspConsent; + }; + + if (gdprConsent) { + if (typeof gdprConsent.gdprApplies !== 'undefined') { + payload.regs.ext.gdpr = gdprConsent.gdprApplies ? 1 : 0; + }; + + if (typeof gdprConsent.consentString !== 'undefined') { + payload.user = { + ext: { consent: gdprConsent.consentString } + }; + }; + }; + }; + + if (bidRequests[0].schain) { + payload.schain = bidRequests[0].schain; + } + + requests.push(formatRequest({ payload, url: VIDEO_ENDPOINT_URL, bidId })); + break; + } + + case BANNER: { + const tags = bidRequests.map(createVideoTag); + const schain = bidRequests[0].schain; + + const payload = { + tags, + sdk, + schain, + }; + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdpr_consent = { + consent_string: bidderRequest.gdprConsent.consentString, + consent_required: bidderRequest.gdprConsent.gdprApplies + }; + + if (bidderRequest.gdprConsent.addtlConsent && bidderRequest.gdprConsent.addtlConsent.indexOf('~') !== -1) { + let ac = bidderRequest.gdprConsent.addtlConsent; + let acStr = ac.substring(ac.indexOf('~') + 1); + payload.gdpr_consent.addtl_consent = acStr.split('.').map(id => parseInt(id, 10)); + } + } + + if (bidderRequest && bidderRequest.uspConsent) { + payload.us_privacy = bidderRequest.uspConsent + } + + return formatRequest({ payload, url: BANNER_ENDPOINT_URL, bidderRequest }); + } + } } } - if (bidderRequest && bidderRequest.uspConsent) { - payload.us_privacy = bidderRequest.uspConsent - } - - return formatRequest(payload, bidderRequest); + return requests; }, /** @@ -74,139 +148,45 @@ 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(serverResponse, { bidderRequest }) { + interpretResponse: function(serverResponse, { bidderRequest, ...bidRequest }) { serverResponse = serverResponse.body; + const currency = serverResponse.cur; const bids = []; if (serverResponse.tags) { serverResponse.tags.forEach(serverBid => { const rtbBid = getRtbBid(serverBid); if (rtbBid && rtbBid.cpm !== 0 && rtbBid.ad_type == VIDEO) { - bids.push(newBid(serverBid, rtbBid, bidderRequest)); + bids.push(bannerBid(serverBid, rtbBid, bidderRequest, MARGIN)); } }); } - return bids; - } - -} - -function getSizes(request) { - let sizes = request.sizes; - if (!sizes && request.mediaTypes && request.mediaTypes.banner && request.mediaTypes.banner.sizes) { - sizes = request.mediaTypes.banner.sizes; - } - if (Array.isArray(sizes) && !Array.isArray(sizes[0])) { - sizes = [sizes[0], sizes[1]]; - } - if (!Array.isArray(sizes) || !Array.isArray(sizes[0])) { - sizes = [[0, 0]]; - } - - return sizes; -} - -function formatRequest(payload, bidderRequest) { - const options = { - withCredentials: true - }; - const request = { - method: 'POST', - url: ENDPOINT_URL, - data: JSON.stringify(payload), - bidderRequest, - options - }; - - return request; -} - -/** - * Create video auction. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ -function createVideoTag(bid) { - const tag = {}; - tag.id = parseInt(bid.params.placementId, 10); - tag.gpid = 'targetVideo'; - tag.sizes = getSizes(bid); - tag.primary_size = tag.sizes[0]; - tag.ad_types = [VIDEO]; - tag.uuid = bid.bidId; - tag.allow_smaller_sizes = false; - tag.use_pmt_rule = false; - tag.prebid = true; - tag.disable_psa = true; - tag.hb_source = 1; - tag.require_asset_url = true; - tag.video = { - playback_method: 2, - skippable: true - }; - - return tag; -} - -/** - * Unpack the Server's Bid into a Prebid-compatible one. - * @param serverBid - * @param rtbBid - * @param bidderRequest - * @return Bid - */ -function newBid(serverBid, rtbBid, bidderRequest) { - const bidRequest = getBidRequest(serverBid.uuid, [bidderRequest]); - const sizes = getSizes(bidRequest); - const bid = { - requestId: serverBid.uuid, - cpm: rtbBid.cpm / MARGIN, - creativeId: rtbBid.creative_id, - dealId: rtbBid.deal_id, - currency: 'USD', - netRevenue: true, - width: sizes[0][0], - height: sizes[0][1], - ttl: 300, - adUnitCode: bidRequest.adUnitCode, - appnexus: { - buyerMemberId: rtbBid.buyer_member_id, - dealPriority: rtbBid.deal_priority, - dealCode: rtbBid.deal_code + if (serverResponse.seatbid) { + _each(serverResponse.seatbid, (resp) => { + _each(resp.bid, (bid) => { + const requestId = bidRequest.bidId; + const params = bidRequest.params; + const vBid = videoBid(bid, requestId, currency, params, TIME_TO_LIVE); + if (bids.length == 0 || bids[0].cpm < vBid.cpm) { + bids[0] = vBid; + } + }); + }); } - }; - - if (rtbBid.rtb.video) { - Object.assign(bid, { - vastImpUrl: rtbBid.notify_url, - ad: getBannerHtml(rtbBid.notify_url + '&redir=' + encodeURIComponent(rtbBid.rtb.video.asset_url)), - ttl: 3600 - }); - } - return bid; -} + return bids; + }, -function getRtbBid(tag) { - return tag && tag.ads && tag.ads.length && find(tag.ads, ad => ad.rtb); -} + /** + * Determine the user sync type (either 'iframe' or 'image') based on syncOptions. + * Construct the sync URL by appending required query parameters such as gdpr, ccpa, and coppa consents. + * Return an array containing an object with the sync type and the constructed URL. + */ + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent, gppConsent) => { + return getSyncResponse(syncOptions, gdprConsent, uspConsent, gppConsent, 'targetvideo'); + } -function getBannerHtml(vastUrl) { - return ` - - - - - - - -
- - - - `; } registerBidder(spec); diff --git a/modules/targetVideoBidAdapter.md b/modules/targetVideoBidAdapter.md index 557c9f94410..a34ad0aff27 100644 --- a/modules/targetVideoBidAdapter.md +++ b/modules/targetVideoBidAdapter.md @@ -3,17 +3,17 @@ ``` Module Name: Target Video Bid Adapter Module Type: Bidder Adapter -Maintainer: grajzer@gmail.com +Maintainers: grajzer@gmail.com, danijel.ristic@target-video.com ``` # Description Connects to Appnexus exchange for bids. -TargetVideo bid adapter supports Banner. +TargetVideo bid adapter supports Banner and Video. # Test Parameters -``` +```js var adUnits = [ // Banner adUnit { @@ -29,6 +29,23 @@ var adUnits = [ placementId: 13232361 } }] + }, + // Video adUnit + { + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } + }, + bids: [{ + bidder: 'targetVideo', + params: { + placementId: 12345, + reserve: 0, + } + }] } ]; ``` diff --git a/modules/gdprEnforcement.js b/modules/tcfControl.js similarity index 90% rename from modules/gdprEnforcement.js rename to modules/tcfControl.js index caa498c7364..603c91443a3 100644 --- a/modules/gdprEnforcement.js +++ b/modules/tcfControl.js @@ -6,8 +6,8 @@ import {deepAccess, logError, logWarn} from '../src/utils.js'; import {config} from '../src/config.js'; import adapterManager, {gdprDataHandler} from '../src/adapterManager.js'; import * as events from '../src/events.js'; -import { EVENTS } from '../src/constants.js'; -import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../src/consentHandler.js'; +import {EVENTS} from '../src/constants.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../src/consentHandler.js'; import { MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, @@ -23,10 +23,14 @@ import { import {registerActivityControl} from '../src/activities/rules.js'; import { ACTIVITY_ACCESS_DEVICE, - ACTIVITY_ENRICH_EIDS, ACTIVITY_ENRICH_UFPD, + ACTIVITY_ENRICH_EIDS, + ACTIVITY_ENRICH_UFPD, ACTIVITY_FETCH_BIDS, ACTIVITY_REPORT_ANALYTICS, - ACTIVITY_SYNC_USER, ACTIVITY_TRANSMIT_EIDS, ACTIVITY_TRANSMIT_PRECISE_GEO, ACTIVITY_TRANSMIT_UFPD + ACTIVITY_SYNC_USER, + ACTIVITY_TRANSMIT_EIDS, + ACTIVITY_TRANSMIT_PRECISE_GEO, + ACTIVITY_TRANSMIT_UFPD } from '../src/activities/activities.js'; export const STRICT_STORAGE_ENFORCEMENT = 'strictStorageEnforcement'; @@ -37,7 +41,7 @@ export const ACTIVE_RULES = { }; const CONSENT_PATHS = { - purpose: 'purpose.consents', + purpose: false, feature: 'specialFeatureOptins' }; @@ -98,6 +102,7 @@ const RULE_HANDLES = []; // in JS we do not have access to the GVL; assume that everyone declares legitimate interest for basic ads const LI_PURPOSES = [2]; +const PUBLISHER_LI_PURPOSES = [2, 7, 9, 10]; /** * Retrieve a module's GVL ID. @@ -111,7 +116,7 @@ export function getGvlid(moduleType, moduleName, fallbackFn) { if (gvlMapping && gvlMapping[moduleName]) { return gvlMapping[moduleName]; } else if (moduleType === MODULE_TYPE_PREBID) { - return moduleName === 'cdep' ? FIRST_PARTY_GVLID : VENDORLESS_GVLID; + return VENDORLESS_GVLID; } else { let {gvlid, modules} = GDPR_GVLIDS.get(moduleName); if (gvlid == null && Object.keys(modules).length > 0) { @@ -163,15 +168,25 @@ export function shouldEnforce(consentData, purpose, name) { return consentData && consentData.gdprApplies; } -function getConsent(consentData, type, id, gvlId) { - let purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${id}`); - let vendor = !!deepAccess(consentData, `vendorData.vendor.consents.${gvlId}`); +function getConsentOrLI(consentData, path, id, acceptLI) { + const data = deepAccess(consentData, `vendorData.${path}`); + return !!data?.consents?.[id] || (acceptLI && !!data?.legitimateInterests?.[id]); +} - if (type === 'purpose' && LI_PURPOSES.includes(id)) { - purpose ||= !!deepAccess(consentData, `vendorData.purpose.legitimateInterests.${id}`); - vendor ||= !!deepAccess(consentData, `vendorData.vendor.legitimateInterests.${gvlId}`); +function getConsent(consentData, type, purposeNo, gvlId) { + let purpose; + if (CONSENT_PATHS[type] !== false) { + purpose = !!deepAccess(consentData, `vendorData.${CONSENT_PATHS[type]}.${purposeNo}`); + } else { + const [path, liPurposes] = gvlId === VENDORLESS_GVLID + ? ['publisher', PUBLISHER_LI_PURPOSES] + : ['purpose', LI_PURPOSES]; + purpose = getConsentOrLI(consentData, path, purposeNo, liPurposes.includes(purposeNo)); + } + return { + purpose, + vendor: getConsentOrLI(consentData, 'vendor', gvlId, LI_PURPOSES.includes(purposeNo)) } - return {purpose, vendor}; } /** @@ -192,14 +207,7 @@ export function validateRules(rule, consentData, currentModule, gvlId) { } const vendorConsentRequred = rule.enforceVendor && !((gvlId === VENDORLESS_GVLID || (rule.softVendorExceptions || []).includes(currentModule))); const {purpose, vendor} = getConsent(consentData, ruleOptions.type, ruleOptions.id, gvlId); - - let validation = (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); - - if (gvlId === FIRST_PARTY_GVLID) { - validation = (!rule.enforcePurpose || !!deepAccess(consentData, `vendorData.publisher.consents.${ruleOptions.id}`)); - } - - return validation; + return (!rule.enforcePurpose || purpose) && (!vendorConsentRequred || vendor); } function gdprRule(purposeNo, checkConsent, blocked = null, gvlidFallback = () => null) { diff --git a/modules/teadsBidAdapter.js b/modules/teadsBidAdapter.js index a3c8d3e24dc..d7b9ef9be2b 100644 --- a/modules/teadsBidAdapter.js +++ b/modules/teadsBidAdapter.js @@ -2,6 +2,7 @@ import {getValue, logError, deepAccess, parseSizesInput, isArray, getBidIdParame import {registerBidder} from '../src/adapters/bidderFactory.js'; import {getStorageManager} from '../src/storageManager.js'; import {isAutoplayEnabled} from '../libraries/autoplayDetection/autoplay.js'; +import {getDM, getHC, getHLen} from '../libraries/navigatorData/navigatorData.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -61,15 +62,16 @@ export const spec = { networkBandwidth: getConnectionDownLink(window.navigator), timeToFirstByte: getTimeToFirstByte(window), data: bids, + device: bidderRequest?.ortb2?.device || {}, deviceWidth: screen.width, deviceHeight: screen.height, devicePixelRatio: topWindow.devicePixelRatio, screenOrientation: screen.orientation?.type, - historyLength: topWindow.history?.length, + historyLength: getHLen(), viewportHeight: topWindow.visualViewport?.height, viewportWidth: topWindow.visualViewport?.width, - hardwareConcurrency: topWindow.navigator?.hardwareConcurrency, - deviceMemory: topWindow.navigator?.deviceMemory, + hardwareConcurrency: getHC(), + deviceMemory: getDM(), hb_version: '$prebid.version$', ...getSharedViewerIdParameters(validBidRequests), ...getFirstPartyTeadsIdParameter(validBidRequests) @@ -81,6 +83,18 @@ export const spec = { payload.schain = firstBidRequest.schain; } + let gpp = bidderRequest.gppConsent; + if (bidderRequest && gpp) { + let isValidConsentString = typeof gpp.gppString === 'string'; + let validateApplicableSections = + Array.isArray(gpp.applicableSections) && + gpp.applicableSections.every((section) => typeof (section) === 'number') + payload.gpp = { + consentString: isValidConsentString ? gpp.gppString : '', + applicableSectionIds: validateApplicableSections ? gpp.applicableSections : [], + }; + } + let gdpr = bidderRequest.gdprConsent; if (bidderRequest && gdpr) { let isCmp = typeof gdpr.gdprApplies === 'boolean'; diff --git a/modules/telariaBidAdapter.js b/modules/telariaBidAdapter.js index 38eefd447a8..4ad544aaa50 100644 --- a/modules/telariaBidAdapter.js +++ b/modules/telariaBidAdapter.js @@ -1,6 +1,7 @@ import { logError, isEmpty, deepAccess, triggerPixel, logWarn, isArray } from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {VIDEO} from '../src/mediaTypes.js'; +import {getSupplyChain} from '../libraries/riseUtils/index.js'; const BIDDER_CODE = 'telaria'; const DOMAIN = 'tremorhub.com'; @@ -127,37 +128,6 @@ function getDefaultSrcPageUrl() { return encodeURIComponent(document.location.href); } -function getEncodedValIfNotEmpty(val) { - return (val !== '' && val !== undefined) ? encodeURIComponent(val) : ''; -} - -/** - * Converts the schain object to a url param value. Please refer to - * https://github.com/InteractiveAdvertisingBureau/openrtb/blob/master/supplychainobject.md - * (schain for non ORTB section) for more information - * @param schainObject - * @returns {string} - */ -function getSupplyChainAsUrlParam(schainObject) { - if (isEmpty(schainObject)) { - return ''; - } - - let scStr = `&schain=${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; -} - function getUrlParams(params, schainFromBidRequest) { let urlSuffix = ''; @@ -167,7 +137,7 @@ function getUrlParams(params, schainFromBidRequest) { urlSuffix += `&${key}=${params[key]}`; } } - urlSuffix += getSupplyChainAsUrlParam(!isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); + urlSuffix += getSupplyChain(!isEmpty(schainFromBidRequest) ? schainFromBidRequest : params['schain']); } return urlSuffix; diff --git a/modules/terceptAnalyticsAdapter.js b/modules/terceptAnalyticsAdapter.js index 089f8d917d6..289a19beb53 100644 --- a/modules/terceptAnalyticsAdapter.js +++ b/modules/terceptAnalyticsAdapter.js @@ -3,7 +3,6 @@ import { ajax } from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS } from '../src/constants.js'; -import {getGlobal} from '../src/prebidGlobal.js'; const emptyUrl = ''; const analyticsType = 'endpoint'; @@ -124,7 +123,7 @@ function send(data, status) { search: { auctionTimestamp: auctionTimestamp, terceptAnalyticsVersion: terceptAnalyticsVersion, - prebidVersion: getGlobal().version + prebidVersion: 'v' + '$prebid.version$' } }); diff --git a/modules/theAdxBidAdapter.js b/modules/theAdxBidAdapter.js index f19f7cfe515..6d3c2e07b84 100644 --- a/modules/theAdxBidAdapter.js +++ b/modules/theAdxBidAdapter.js @@ -20,6 +20,7 @@ import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; const BIDDER_CODE = 'theadx'; const ENDPOINT_URL = 'https://ssp.theadx.com/request'; +const ENDPOINT_TR_URL = 'https://ssptr.theadx.com/request'; const NATIVEASSETNAMES = { 0: 'title', @@ -125,7 +126,7 @@ const NATIVEPROBS = { export const spec = { code: BIDDER_CODE, - aliases: ['theadx'], // short code + aliases: ['theadx', 'theAdx'], // short code supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -160,10 +161,11 @@ export const spec = { if (!isEmpty(validBidRequests)) { results = validBidRequests.map( bidRequest => { + let url = `${getRegionEndPoint(bidRequest)}?tagid=${bidRequest.params.tagId}`; return { method: requestType, type: requestType, - url: `${ENDPOINT_URL}?tagid=${bidRequest.params.tagId}`, + url: url, options: { withCredentials: true, }, @@ -500,6 +502,14 @@ let generateImpBody = (bidRequest, bidderRequest) => { return result; } +let getRegionEndPoint = (bidRequest) => { + if (bidRequest && bidRequest.params && bidRequest.params.region) { + if (bidRequest.params.region.toLowerCase() == 'tr') { + return ENDPOINT_TR_URL; + } + } + return ENDPOINT_URL; +}; let generatePayload = (bidRequest, bidderRequest) => { // Generate the expected OpenRTB payload @@ -511,7 +521,38 @@ let generatePayload = (bidRequest, bidderRequest) => { imp: [generateImpBody(bidRequest, bidderRequest)], }; // return payload; + let eids = getEids(bidRequest); + if (Object.keys(eids).length > 0) { + payload.ext = eids; + } return JSON.stringify(payload); }; +function getEids(bidRequest) { + let eids = {} + + let uId2 = deepAccess(bidRequest, 'userId.uid2.id'); + if (uId2) { + eids['uid2'] = uId2; + } + + let id5 = deepAccess(bidRequest, 'userId.id5id.uid'); + if (id5) { + eids['id5id'] = id5; + let id5Linktype = deepAccess(bidRequest, 'userId.id5id.ext.linkType'); + if (id5Linktype) { + eids['id5_linktype'] = id5Linktype; + } + } + let netId = deepAccess(bidRequest, 'userId.netId'); + if (netId) { + eids['netid'] = netId; + } + let sharedId = deepAccess(bidRequest, 'userId.sharedid.id'); + if (sharedId) { + eids['sharedid'] = sharedId; + } + return eids; +}; + registerBidder(spec); diff --git a/modules/tncIdSystem.js b/modules/tncIdSystem.js index 59254a9430f..9d3187462be 100644 --- a/modules/tncIdSystem.js +++ b/modules/tncIdSystem.js @@ -1,6 +1,7 @@ import { submodule } from '../src/hook.js'; import { logInfo } from '../src/utils.js'; import { loadExternalScript } from '../src/adloader.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; const MODULE_NAME = 'tncId'; let url = null; @@ -10,17 +11,16 @@ const waitTNCScript = (tncNS) => { var tnc = window[tncNS]; if (!tnc) reject(new Error('No TNC Object')); if (tnc.tncid) resolve(tnc.tncid); - tnc.ready(() => { - tnc = window[tncNS]; - if (tnc.tncid) resolve(tnc.tncid); - else tnc.on('data-sent', () => resolve(tnc.tncid)); + tnc.ready(async () => { + let tncid = await tnc.getTNCID('prebid'); + resolve(tncid); }); }); } const loadRemoteScript = () => { return new Promise((resolve) => { - loadExternalScript(url, MODULE_NAME, resolve); + loadExternalScript(url, MODULE_TYPE_UID, MODULE_NAME, resolve); }) } @@ -31,7 +31,6 @@ const tncCallback = function (cb) { tncNS = '__tncPbjs'; promiseArray.push(loadRemoteScript()); } - return Promise.all(promiseArray).then(() => waitTNCScript(tncNS)).then(cb).catch(() => cb()); } diff --git a/modules/tncIdSystem.md b/modules/tncIdSystem.md index f0f98e9098f..b94d03c8e85 100644 --- a/modules/tncIdSystem.md +++ b/modules/tncIdSystem.md @@ -10,6 +10,8 @@ gulp build --modules=tncIdSystem,userId ### TNCIDIdSystem module Configuration +Disclosure: This module loads external script unreviewed by the prebid.js community + You can configure this submodule in your `userSync.userIds[]` configuration: ```javascript diff --git a/modules/topLevelPaapi.js b/modules/topLevelPaapi.js new file mode 100644 index 00000000000..86164668a90 --- /dev/null +++ b/modules/topLevelPaapi.js @@ -0,0 +1,214 @@ +import {submodule} from '../src/hook.js'; +import {config} from '../src/config.js'; +import {logError, logInfo, logWarn, mergeDeep} from '../src/utils.js'; +import {auctionStore} from '../libraries/weakStore/weakStore.js'; +import {getGlobal} from '../src/prebidGlobal.js'; +import {emit} from '../src/events.js'; +import {BID_STATUS, EVENTS} from '../src/constants.js'; +import {GreedyPromise} from '../src/utils/promise.js'; +import {getBidToRender, getRenderingData, markWinningBid} from '../src/adRendering.js'; + +let getPAAPIConfig, expandFilters, moduleConfig; + +const paapiBids = auctionStore(); +const MODULE_NAME = 'topLevelPaapi'; + +config.getConfig('paapi', (cfg) => { + moduleConfig = cfg.paapi?.topLevelSeller; + if (moduleConfig) { + getBidToRender.before(renderPaapiHook); + getBidToRender.after(renderOverrideHook); + getRenderingData.before(getRenderingDataHook); + markWinningBid.before(markWinningBidHook); + } else { + getBidToRender.getHooks({hook: renderPaapiHook}).remove(); + getBidToRender.getHooks({hook: renderOverrideHook}).remove(); + getRenderingData.getHooks({hook: getRenderingDataHook}).remove(); + markWinningBid.getHooks({hook: markWinningBidHook}).remove(); + } +}); + +function isPaapiBid(bid) { + return bid?.source === 'paapi'; +} + +function bidIfRenderable(bid) { + if (bid && !bid.urn) { + logWarn(MODULE_NAME, 'rendering in fenced frames is not supported. Consider using resolveToConfig: false', bid); + return; + } + return bid; +} + +const forRenderStack = []; + +function renderPaapiHook(next, adId, forRender = true, override = GreedyPromise.resolve()) { + forRenderStack.push(forRender); + const ids = parsePaapiAdId(adId); + if (ids) { + override = override.then((bid) => { + if (bid) return bid; + const [auctionId, adUnitCode] = ids; + return paapiBids(auctionId)?.[adUnitCode]?.then(bid => { + if (!bid) { + logWarn(MODULE_NAME, `No PAAPI bid found for auctionId: "${auctionId}", adUnit: "${adUnitCode}"`); + } + return bidIfRenderable(bid); + }); + }); + } + next(adId, forRender, override); +} + +function renderOverrideHook(next, bidPm) { + const forRender = forRenderStack.pop(); + if (moduleConfig?.overrideWinner) { + bidPm = bidPm.then((bid) => { + if (isPaapiBid(bid) || bid?.status === BID_STATUS.RENDERED) return bid; + return getPAAPIBids({adUnitCode: bid.adUnitCode}).then(res => { + let paapiBid = bidIfRenderable(res[bid.adUnitCode]); + if (paapiBid) { + if (!forRender) return paapiBid; + if (forRender && paapiBid.status !== BID_STATUS.RENDERED) { + paapiBid.overriddenAdId = bid.adId; + logInfo(MODULE_NAME, 'overriding contextual bid with PAAPI bid', bid, paapiBid) + return paapiBid; + } + } + return bid; + }); + }); + } + next(bidPm); +} + +export function getRenderingDataHook(next, bid, options) { + if (isPaapiBid(bid)) { + next.bail({ + width: bid.width, + height: bid.height, + adUrl: bid.urn + }); + } else { + next(bid, options); + } +} + +export function markWinningBidHook(next, bid) { + if (isPaapiBid(bid)) { + emit(EVENTS.BID_WON, bid); + next.bail(); + } else { + next(bid); + } +} + +function getBaseAuctionConfig() { + if (moduleConfig?.auctionConfig) { + return Object.assign({ + resolveToConfig: false + }, moduleConfig.auctionConfig); + } +} + +function onAuctionConfig(auctionId, auctionConfigs) { + const base = getBaseAuctionConfig(); + if (base) { + Object.entries(auctionConfigs).forEach(([adUnitCode, auctionConfig]) => { + mergeDeep(auctionConfig, base); + if (moduleConfig.autorun ?? true) { + getPAAPIBids({adUnitCode, auctionId}); + } + }); + } +} + +export function parsePaapiSize(size) { + /* From https://github.com/WICG/turtledove/blob/main/FLEDGE.md#12-interest-group-attributes: + * Each size has the format {width: widthVal, height: heightVal}, + * where the values can have either pixel units (e.g. 100 or '100px') or screen dimension coordinates (e.g. 100sw or 100sh). + */ + if (typeof size === 'number') return size; + if (typeof size === 'string') { + const px = /^(\d+)(px)?$/.exec(size)?.[1]; + if (px) { + return parseInt(px, 10); + } + } + return null; +} + +export function getPaapiAdId(auctionId, adUnitCode) { + return `paapi:/${auctionId.replace(/:/g, '::')}/:/${adUnitCode.replace(/:/g, '::')}`; +} + +export function parsePaapiAdId(adId) { + const match = /^paapi:\/(.*)\/:\/(.*)$/.exec(adId); + if (match) { + return [match[1], match[2]].map(s => s.replace(/::/g, ':')); + } +} + +/** + * Returns the PAAPI runAdAuction result for the given filters, as a map from ad unit code to auction result + * (an object with `width`, `height`, and one of `urn` or `frameConfig`). + * + * @param filters + * @param raa + * @return {Promise<{[p: string]: any}>} + */ +export function getPAAPIBids(filters, raa = (...args) => navigator.runAdAuction(...args)) { + return Promise.all( + Object.entries(expandFilters(filters)) + .map(([adUnitCode, auctionId]) => { + const bids = paapiBids(auctionId); + if (bids && !bids.hasOwnProperty(adUnitCode)) { + const auctionConfig = getPAAPIConfig({adUnitCode, auctionId})[adUnitCode]; + if (auctionConfig) { + emit(EVENTS.RUN_PAAPI_AUCTION, { + auctionId, + adUnitCode, + auctionConfig + }); + bids[adUnitCode] = new Promise((resolve, reject) => raa(auctionConfig).then(resolve, reject)) + .then(result => { + if (result) { + const bid = { + source: 'paapi', + adId: getPaapiAdId(auctionId, adUnitCode), + width: parsePaapiSize(auctionConfig.requestedSize?.width), + height: parsePaapiSize(auctionConfig.requestedSize?.height), + adUnitCode, + auctionId, + [typeof result === 'string' ? 'urn' : 'frameConfig']: result, + auctionConfig, + }; + emit(EVENTS.PAAPI_BID, bid); + return bid; + } else { + emit(EVENTS.PAAPI_NO_BID, {auctionId, adUnitCode, auctionConfig}); + return null; + } + }).catch(error => { + logError(MODULE_NAME, `error (auction "${auctionId}", adUnit "${adUnitCode}"):`, error); + emit(EVENTS.PAAPI_ERROR, {auctionId, adUnitCode, error, auctionConfig}); + return null; + }); + } + } + return bids?.[adUnitCode]?.then(res => [adUnitCode, res]); + }).filter(e => e) + ).then(result => Object.fromEntries(result)); +} + +getGlobal().getPAAPIBids = (filters) => getPAAPIBids(filters); + +export const topLevelPAAPI = { + name: MODULE_NAME, + init(params) { + getPAAPIConfig = params.getPAAPIConfig; + expandFilters = params.expandFilters; + }, + onAuctionConfig +}; +submodule('paapi', topLevelPAAPI); diff --git a/modules/topicsFpdModule.js b/modules/topicsFpdModule.js index 72f4068af7f..8df01fcd599 100644 --- a/modules/topicsFpdModule.js +++ b/modules/topicsFpdModule.js @@ -21,35 +21,6 @@ export function reset() { iframeLoadedURL = []; } -const bidderIframeList = { - maxTopicCaller: 4, - bidders: [{ - bidder: 'pubmatic', - iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' - }, { - bidder: 'rtbhouse', - iframeURL: 'https://topics.authorizedvault.com/topicsapi.html' - }, { - bidder: 'openx', - iframeURL: 'https://pa.openx.net/topics_frame.html' - }, { - bidder: 'improvedigital', - iframeURL: 'https://hb.360yield.com/privacy-sandbox/topics.html' - }, { - bidder: 'onetag', - iframeURL: 'https://onetag-sys.com/static/topicsapi.html' - }, { - bidder: 'taboola', - iframeURL: 'https://cdn.taboola.com/libtrc/static/topics/taboola-prebid-browsing-topics.html' - }, { - bidder: 'discovery', - iframeURL: 'https://api.popin.cc/topic/prebid-topics-frame.html' - }, { - bidder: 'undertone', - iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html' - }] -} - export const coreStorage = getCoreStorageManager(MODULE_NAME); export const topicStorageName = 'prebid:topics'; export const lastUpdated = 'lastUpdated'; @@ -158,8 +129,8 @@ export function processFpd(config, {global}, {data = topicsData} = {}) { */ export function getCachedTopics() { let cachedTopicData = []; - const topics = config.getConfig('userSync.topics') || bidderIframeList; - const bidderList = topics.bidders || []; + const topics = config.getConfig('userSync.topics'); + const bidderList = topics?.bidders || []; let storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); storedSegments && storedSegments.forEach((value, cachedBidder) => { // Check bidder exist in config for cached bidder data and then only retrieve the cached data @@ -198,7 +169,8 @@ export function receiveMessage(evt) { /** Function to store Topics data received from iframe in storage(name: "prebid:topics") - * @param {Topics} topics + * @param {string} bidder + * @param {object} topics */ export function storeInLocalStorage(bidder, topics) { const storedSegments = new Map(safeJSONParse(coreStorage.getDataFromLocalStorage(topicStorageName))); @@ -240,7 +212,7 @@ function listenMessagesFromTopicIframe() { */ export function loadTopicsForBidders(doc = document) { if (!isTopicsSupported(doc)) return; - const topics = config.getConfig('userSync.topics') || bidderIframeList; + const topics = config.getConfig('userSync.topics'); if (topics) { listenMessagesFromTopicIframe(); diff --git a/modules/topicsFpdModule.md b/modules/topicsFpdModule.md index d187645f520..1f299a9eabe 100644 --- a/modules/topicsFpdModule.md +++ b/modules/topicsFpdModule.md @@ -68,6 +68,10 @@ pbjs.setConfig({ bidder: 'undertone', iframeURL: 'https://creative-p.undertone.com/spk-public/topics_frame.html', expiry: 7 // Configurable expiry days + },{ + bidder: 'vidazoo', + iframeURL: 'https://static.vidazoo.com/topics_api/topics_frame.html', + expiry: 7 // Configurable expiry days }] } .... diff --git a/modules/trafficgateBidAdapter.js b/modules/trafficgateBidAdapter.js index fcd84306099..d30d79ef3a6 100644 --- a/modules/trafficgateBidAdapter.js +++ b/modules/trafficgateBidAdapter.js @@ -2,7 +2,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {ortbConverter} from '../libraries/ortbConverter/converter.js'; import {deepAccess, mergeDeep} from '../src/utils.js'; -import {convertTypes} from '../libraries/transformParamsUtils/convertTypes.js'; const BIDDER_CODE = 'trafficgate'; const URL = 'https://[HOST].bc-plugin.com/prebidjs' @@ -13,7 +12,6 @@ export const spec = { isBidRequestValid, buildRequests, interpretResponse, - transformBidParams, isBannerBid }; @@ -88,14 +86,6 @@ const converter = ortbConverter({ } }); -function transformBidParams(params, isOpenRtb) { - return convertTypes({ - 'customFloor': 'number', - 'placementId': 'number', - 'host': 'string' - }, params); -} - function isBidRequestValid(bidRequest) { const isValid = bidRequest.params.placementId && bidRequest.params.host; if (!isValid) { diff --git a/modules/tripleliftBidAdapter.js b/modules/tripleliftBidAdapter.js index fc0d73dc44b..0c8bd330e11 100644 --- a/modules/tripleliftBidAdapter.js +++ b/modules/tripleliftBidAdapter.js @@ -58,8 +58,8 @@ export const tripleliftAdapterSpec = { tlCall = tryAppendQueryString(tlCall, 'us_privacy', bidderRequest.uspConsent); } - if (bidderRequest && bidderRequest.fledgeEnabled) { - tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.fledgeEnabled); + if (bidderRequest?.paapi?.enabled) { + tlCall = tryAppendQueryString(tlCall, 'fledge', bidderRequest.paapi.enabled); } if (config.getConfig('coppa') === true) { @@ -96,7 +96,7 @@ export const tripleliftAdapterSpec = { logMessage('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } else { return bids; @@ -236,20 +236,7 @@ function _getORTBVideo(bidRequest) { } catch (err) { logWarn('Video size not defined', err); } - // honor existing publisher settings - if (video.context === 'instream') { - if (!video.placement) { - video.placement = 1; - } - } - if (video.context === 'outstream') { - if (!video.placement) { - video.placement = 3 - } else if ([3, 4, 5].indexOf(video.placement) === -1) { - logMessage(`video.placement value of ${video.placement} is invalid for outstream context. Setting placement to 3`) - video.placement = 3 - } - } + if (video.playbackmethod && Number.isInteger(video.playbackmethod)) { video.playbackmethod = Array.from(String(video.playbackmethod), Number); } @@ -268,7 +255,7 @@ function _getFloor (bid) { mediaType: _isVideoBidRequest(bid) ? 'video' : 'banner', size: '*' }); - if (typeof floorInfo === 'object' && + if (utils.isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = parseFloat(floorInfo.floor); } diff --git a/modules/truereachBidAdapter.js b/modules/truereachBidAdapter.js index 8b1656ec7a2..9dda76f6518 100755 --- a/modules/truereachBidAdapter.js +++ b/modules/truereachBidAdapter.js @@ -11,7 +11,7 @@ export const spec = { supportedMediaTypes: SUPPORTED_AD_TYPES, isBidRequestValid: function (bidRequest) { - return (bidRequest.params.site_id && bidRequest.params.bidfloor && + return (bidRequest.params.site_id && deepAccess(bidRequest, 'mediaTypes.banner') && (deepAccess(bidRequest, 'mediaTypes.banner.sizes.length') > 0)); }, @@ -116,8 +116,6 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { adH = adSizes[0][1]; } - let bidFloor = Number(0); - let domain = window.location.host; let page = window.location.host + window.location.pathname + location.search + location.hash; @@ -129,8 +127,7 @@ function buildCommonQueryParamsFromBids(validBidRequests, bidderRequest) { banner: { w: adW, h: adH - }, - bidfloor: bidFloor + } } ], site: { diff --git a/modules/ttdBidAdapter.js b/modules/ttdBidAdapter.js index d7705f2f5df..e9d0d3ca9f1 100644 --- a/modules/ttdBidAdapter.js +++ b/modules/ttdBidAdapter.js @@ -2,7 +2,8 @@ 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'; -import {isNumber} from '../src/utils.js'; +import { isNumber } from '../src/utils.js'; +import { getConnectionType } from '../libraries/connectionInfo/connectionUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -13,10 +14,11 @@ import {isNumber} from '../src/utils.js'; * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync */ -const BIDADAPTERVERSION = 'TTD-PREBID-2023.09.05'; +const BIDADAPTERVERSION = 'TTD-PREBID-2024.07.26'; const BIDDER_CODE = 'ttd'; const BIDDER_CODE_LONG = 'thetradedesk'; const BIDDER_ENDPOINT = 'https://direct.adsrvr.org/bid/bidder/'; +const BIDDER_ENDPOINT_HTTP2 = 'https://d2.adsrvr.org/bid/bidder/'; const USER_SYNC_ENDPOINT = 'https://match.adsrvr.org'; const MEDIA_TYPE = { @@ -99,33 +101,6 @@ function getDevice(firstPartyData) { return device; }; -function getConnectionType() { - const connection = navigator.connection || navigator.webkitConnection; - if (!connection) { - return 0; - } - switch (connection.type) { - case 'ethernet': - return 1; - case 'wifi': - return 2; - case 'cellular': - switch (connection.effectiveType) { - case 'slow-2g': - case '2g': - return 4; - case '3g': - return 5; - case '4g': - return 6; - default: - return 3; - } - default: - return 0; - } -} - function getUser(bidderRequest, firstPartyData) { let user = {}; if (bidderRequest.gdprConsent) { @@ -241,7 +216,7 @@ function banner(bid) { }, optionalParams); - const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); + const battr = utils.deepAccess(bid, 'ortb2Imp.banner.battr'); if (battr) { banner.battr = battr; } @@ -318,7 +293,7 @@ function video(bid) { video.maxbitrate = maxbitrate; } - const battr = utils.deepAccess(bid, 'ortb2Imp.battr'); + const battr = utils.deepAccess(bid, 'ortb2Imp.video.battr'); if (battr) { video.battr = battr; } @@ -327,6 +302,13 @@ function video(bid) { } } +function selectEndpoint(params) { + if (params.useHttp2) { + return BIDDER_ENDPOINT_HTTP2; + } + return BIDDER_ENDPOINT; +} + export const spec = { code: BIDDER_CODE, gvlid: 21, @@ -443,7 +425,7 @@ export const spec = { topLevel.pmp = firstPartyData.pmp } - let url = BIDDER_ENDPOINT + bidderRequest.bids[0].params.supplySourceId; + let url = selectEndpoint(bidderRequest.bids[0].params) + bidderRequest.bids[0].params.supplySourceId; let serverRequest = { method: 'POST', diff --git a/modules/twistDigitalBidAdapter.js b/modules/twistDigitalBidAdapter.js index f509e68f9a2..bed9e531a26 100644 --- a/modules/twistDigitalBidAdapter.js +++ b/modules/twistDigitalBidAdapter.js @@ -1,452 +1,28 @@ -import { - _each, - deepAccess, - isFn, - parseSizesInput, - parseUrl, - uniques, - isArray, - formatQS, - triggerPixel -} 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 {bidderSettings} from '../src/bidderSettings.js'; -import {config} from '../src/config.js'; -import {chunk} from '../libraries/chunk/chunk.js'; +import { + isBidRequestValid, createInterpretResponseFn, createUserSyncGetter, createBuildRequestsFn, onBidWon +} from '../libraries/vidazooUtils/bidderUtils.js'; const GVLID = 1292; const DEFAULT_SUB_DOMAIN = 'exchange'; const BIDDER_CODE = 'twistdigital'; const BIDDER_VERSION = '1.0.0'; -const CURRENCY = 'USD'; -const TTL_SECONDS = 60 * 5; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; -export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); export function createDomain(subDomain = DEFAULT_SUB_DOMAIN) { return `https://${subDomain}.twist.win`; } -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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - const {ext} = params; - let {bidFloor} = params; - const hashUrl = hashCode(topWindowUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const pId = extractPID(params); - const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid', deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', '')); - const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); - const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); - const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); - const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sizes: sizes, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - isStorageAllowed: isStorageAllowed, - gpid: gpid, - cat: cat, - contentData, - userData: userData, - pagecat: pagecat, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout, - webSessionId: webSessionId - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (bidderRequest.fledgeEnabled) { - const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); - if (fledge) { - data.fledge = fledge; - } - } - - _each(ext, (value, key) => { - data['ext.' + key] = value; - }); - - return data; -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const {params} = bid; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - return dto; -} - -function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { - const {params} = bidRequests[0]; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = bidRequests.map(bid => { - const sizes = parseSizesInput(bid.sizes); - return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) - }); - const chunkSize = Math.min(20, config.getConfig('twistdigital.chunkSize') || 10); - - const chunkedData = chunk(data, chunkSize); - return chunkedData.map(chunk => { - return { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: { - bids: chunk - } - }; - }); -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - - const singleRequestMode = config.getConfig('twistdigital.singleRequest'); +const buildRequests = createBuildRequestsFn(createDomain, null, storage, BIDDER_CODE, BIDDER_VERSION, true); - const requests = []; +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, true); - if (singleRequestMode) { - // banner bids are sent as a single request - const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); - if (bannerBidRequests.length > 0) { - const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); - requests.push(...singleRequests); - } - - // video bids are sent as a single request for each bid - - const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); - videoBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } else { - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - - const singleRequestMode = config.getConfig('twistdigital.singleRequest'); - const reqBidId = deepAccess(request, 'data.bidId'); - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach((result, i) => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - bidId, - nurl, - advertiserDomains, - metaData, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: (singleRequestMode && bidId) ? bidId : reqBidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (nurl) { - response.nurl = nurl; - } - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; - - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.twist.win/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.twist.win/api/sync/image/${params}` - }); - } - return syncs; -} - -/** - * @param {Bid} bid - */ -function onBidWon(bid) { - if (!bid.nurl) { - return; - } - const wonBid = { - adId: bid.adId, - creativeId: bid.creativeId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - adUnitCode: bid.adUnitCode, - cpm: bid.cpm, - currency: bid.currency, - originalCpm: bid.originalCpm, - originalCurrency: bid.originalCurrency, - netRevenue: bid.netRevenue, - mediaType: bid.mediaType, - timeToRespond: bid.timeToRespond, - status: bid.status, - }; - const qs = formatQS(wonBid); - const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; - triggerPixel(url); -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; -} - -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 getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.twist.win/api/sync/iframe', imageSyncUrl: 'https://sync.twist.win/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/ucfunnelBidAdapter.js b/modules/ucfunnelBidAdapter.js index 19b933a8666..d5017db0705 100644 --- a/modules/ucfunnelBidAdapter.js +++ b/modules/ucfunnelBidAdapter.js @@ -235,7 +235,7 @@ function getFloor(bid, size, mediaTypes) { mediaType: getMediaType(mediaTypes), size: (size) ? [ size[0], size[1] ] : '*', }); - if (bidFloor.currency === CURRENCY) { + if (bidFloor?.currency === CURRENCY) { return bidFloor.floor; } } diff --git a/modules/uid2IdSystem.js b/modules/uid2IdSystem.js index 32d2322e9bd..afdde5f0a7f 100644 --- a/modules/uid2IdSystem.js +++ b/modules/uid2IdSystem.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ /** * This module adds uid2 ID support to the User ID module * The {@link module:modules/userId} module is required. @@ -69,7 +68,7 @@ export const uid2IdSubmodule = { /** * performs action to obtain id and return a value. * @function - * @param {SubmoduleConfig} [configparams] + * @param {SubmoduleConfig} config * @param {ConsentData|undefined} consentData * @returns {uid2Id} */ @@ -109,6 +108,10 @@ function decodeImpl(value) { const result = { uid2: { id: value } }; return result; } + if (value.latestToken === 'optout') { + _logInfo('Found optout token. Refresh is unavailable for this token.'); + return { uid2: { optout: true } }; + } if (Date.now() < value.latestToken.identity_expires) { return { uid2: { id: value.latestToken.advertising_token } }; } diff --git a/modules/uid2IdSystem_shared.js b/modules/uid2IdSystem_shared.js index f3702069d10..a9acfd6291e 100644 --- a/modules/uid2IdSystem_shared.js +++ b/modules/uid2IdSystem_shared.js @@ -1,4 +1,3 @@ -/* eslint-disable no-console */ import { ajax } from '../src/ajax.js'; import { cyrb53Hash } from '../src/utils.js'; @@ -8,12 +7,22 @@ function isValidIdentity(identity) { return !!(typeof identity === 'object' && identity !== null && identity.advertising_token && identity.identity_expires && identity.refresh_from && identity.refresh_token && identity.refresh_expires); } +// Helper function to prepend message +function prependMessage(message) { + return `UID2 shared library - ${message}`; +} + +// Wrapper function for logInfo +function logInfoWrapper(logInfo, ...args) { + logInfo(prependMessage(args[0]), ...args.slice(1)); +} + // This is extracted from an in-progress API client. Once it's available via NPM, this class should be replaced with the NPM package. export class Uid2ApiClient { constructor(opts, clientId, logInfo, logWarn) { this._baseUrl = opts.baseUrl; this._clientVersion = clientId; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); this._logWarn = logWarn; } @@ -36,7 +45,7 @@ export class Uid2ApiClient { if (this.isValidRefreshResponse(response)) { if (response.status === 'success') { return { status: response.status, identity: response.body }; } return response; - } else { return `Response didn't contain a valid status`; } + } else { return prependMessage(`Response didn't contain a valid status`); } } callRefreshApi(refreshDetails) { const url = this._baseUrl + '/v2/token/refresh'; @@ -54,7 +63,7 @@ export class Uid2ApiClient { this._logInfo('No response decryption key available, assuming unencrypted JSON'); const response = JSON.parse(responseText); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } } else { this._logInfo('Decrypting refresh API response'); const encodeResp = this.createArrayBuffer(atob(responseText)); @@ -70,12 +79,12 @@ export class Uid2ApiClient { this._logInfo('Decrypted to:', decryptedResponse); const response = JSON.parse(decryptedResponse); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); - }, (reason) => this._logWarn(`Call to UID2 API failed`, reason)); + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } + }, (reason) => this._logWarn(prependMessage(`Call to UID2 API failed`), reason)); + }, (reason) => this._logWarn(prependMessage(`Call to UID2 API failed`), reason)); } } catch (_err) { - rejectPromise(responseText); + rejectPromise(prependMessage(responseText)); } }, error: (error, xhr) => { @@ -83,9 +92,9 @@ export class Uid2ApiClient { this._logInfo('Error status, assuming unencrypted JSON'); const response = JSON.parse(xhr.responseText); const result = this.ResponseToRefreshResult(response); - if (typeof result === 'string') { rejectPromise(result); } else { resolvePromise(result); } + if (typeof result === 'string') { rejectPromise(prependMessage(result)); } else { resolvePromise(result); } } catch (_e) { - rejectPromise(error) + rejectPromise(prependMessage(error)); } } }, refreshDetails.refresh_token, { method: 'POST', @@ -100,7 +109,7 @@ export class Uid2StorageManager { this._storage = storage; this._preferLocalStorage = preferLocalStorage; this._storageName = storageName; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); } readCookie(cookieName) { return this._storage.cookiesAreEnabled() ? this._storage.getCookie(cookieName) : null; @@ -187,11 +196,15 @@ if (FEATURES.UID2_CSTG) { clientSideTokenGenerator = { isCSTGOptionsValid(maybeOpts, _logWarn) { if (typeof maybeOpts !== 'object' || maybeOpts === null) { - _logWarn('CSTG opts must be an object'); + _logWarn('CSTG is not being used, but is included in the Prebid.js bundle. You can reduce the bundle size by passing "--disable UID2_CSTG" to the Prebid.js build.'); return false; } const opts = maybeOpts; + if (!opts.serverPublicKey && !opts.subscriptionId) { + _logWarn('CSTG has been enabled but its parameters have not been set.'); + return false; + } if (typeof opts.serverPublicKey !== 'string') { _logWarn('CSTG opts.serverPublicKey must be a string'); return false; @@ -389,8 +402,7 @@ if (FEATURES.UID2_CSTG) { this._baseUrl = opts.baseUrl; this._serverPublicKey = opts.cstg.serverPublicKey; this._subscriptionId = opts.cstg.subscriptionId; - this._optoutCheck = opts.cstg.optoutCheck; - this._logInfo = logInfo; + this._logInfo = (...args) => logInfoWrapper(logInfo, ...args); this._logWarn = logWarn; } @@ -447,8 +459,7 @@ if (FEATURES.UID2_CSTG) { } async generateToken(cstgIdentity) { - const requestIdentity = await this.generateCstgRequest(cstgIdentity); - const request = { optout_check: this._optoutCheck, ...requestIdentity }; + const request = await this.generateCstgRequest(cstgIdentity); this._logInfo('Building CSTG request for', request); const box = await UID2CstgBox.build( this.stripPublicKeyPrefix(this._serverPublicKey) @@ -511,11 +522,11 @@ if (FEATURES.UID2_CSTG) { // A 200 should always be a success response. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 200: ${decryptedResponse}` + prependMessage(`API error: Response body was invalid for HTTP status 200: ${decryptedResponse}`) ); } } catch (err) { - rejectPromise(err); + rejectPromise(prependMessage(err)); } }, error: (error, xhr) => { @@ -523,32 +534,32 @@ if (FEATURES.UID2_CSTG) { if (xhr.status === 400) { const response = JSON.parse(xhr.responseText); if (this.isCstgApiClientErrorResponse(response)) { - rejectPromise(`Client error: ${response.message}`); + rejectPromise(prependMessage(`Client error: ${response.message}`)); } else { // A 400 should always be a client error. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 400: ${xhr.responseText}` + prependMessage(`UID2 API error: Response body was invalid for HTTP status 400: ${xhr.responseText}`) ); } } else if (xhr.status === 403) { const response = JSON.parse(xhr.responseText); if (this.isCstgApiForbiddenResponse(xhr)) { - rejectPromise(`Forbidden: ${response.message}`); + rejectPromise(prependMessage(`Forbidden: ${response.message}`)); } else { // A 403 should always be a forbidden response. // Something has gone wrong. rejectPromise( - `API error: Response body was invalid for HTTP status 403: ${xhr.responseText}` + prependMessage(`UID2 API error: Response body was invalid for HTTP status 403: ${xhr.responseText}`) ); } } else { rejectPromise( - `API error: Unexpected HTTP status ${xhr.status}: ${error}` + prependMessage(`UID2 API error: Unexpected HTTP status ${xhr.status}: ${error}`) ); } } catch (_e) { - rejectPromise(error); + rejectPromise(prependMessage(error)); } }, }, @@ -677,43 +688,45 @@ if (FEATURES.UID2_CSTG) { } export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { + const logInfo = (...args) => logInfoWrapper(_logInfo, ...args); + let suppliedToken = null; const preferLocalStorage = (config.storage !== 'cookie'); - const storageManager = new Uid2StorageManager(prebidStorageManager, preferLocalStorage, config.internalStorage, _logInfo); - _logInfo(`Module is using ${preferLocalStorage ? 'local storage' : 'cookies'} for internal storage.`); + const storageManager = new Uid2StorageManager(prebidStorageManager, preferLocalStorage, config.internalStorage, logInfo); + logInfo(`Module is using ${preferLocalStorage ? 'local storage' : 'cookies'} for internal storage.`); const isCstgEnabled = clientSideTokenGenerator && clientSideTokenGenerator.isCSTGOptionsValid(config.cstg, _logWarn); if (isCstgEnabled) { - _logInfo(`Module is using client-side token generation.`); + logInfo(`Module is using client-side token generation.`); // Ignores config.paramToken and config.serverCookieName if any is provided suppliedToken = null; } else if (config.paramToken) { suppliedToken = config.paramToken; - _logInfo('Read token from params', suppliedToken); + logInfo('Read token from params', suppliedToken); } else if (config.serverCookieName) { suppliedToken = storageManager.readProvidedCookie(config.serverCookieName); - _logInfo('Read token from server-supplied cookie', suppliedToken); + logInfo('Read token from server-supplied cookie', suppliedToken); } let storedTokens = storageManager.getStoredValueWithFallback(); - _logInfo('Loaded module-stored tokens:', storedTokens); + logInfo('Loaded module-stored tokens:', storedTokens); if (storedTokens && typeof storedTokens === 'string') { // Stored value is a plain token - if no token is supplied, just use the stored value. if (!suppliedToken && !isCstgEnabled) { - _logInfo('Returning legacy cookie value.'); + logInfo('Returning legacy cookie value.'); return { id: storedTokens }; } // Otherwise, ignore the legacy value - it should get over-written later anyway. - _logInfo('Discarding superseded legacy cookie.'); + logInfo('Discarding superseded legacy cookie.'); storedTokens = null; } if (suppliedToken && storedTokens) { if (storedTokens.originalToken?.advertising_token !== suppliedToken.advertising_token) { - _logInfo('Server supplied new token - ignoring stored value.', storedTokens.originalToken?.advertising_token, suppliedToken.advertising_token); + logInfo('Server supplied new token - ignoring stored value.', storedTokens.originalToken?.advertising_token, suppliedToken.advertising_token); // Stored token wasn't originally sourced from the provided token - ignore the stored value. A new user has logged in? storedTokens = null; } @@ -722,16 +735,16 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { if (FEATURES.UID2_CSTG && isCstgEnabled) { const cstgIdentity = clientSideTokenGenerator.getValidIdentity(config.cstg, _logWarn); if (cstgIdentity) { - if (storedTokens && clientSideTokenGenerator.isStoredTokenInvalid(cstgIdentity, storedTokens, _logInfo, _logWarn)) { + if (storedTokens && clientSideTokenGenerator.isStoredTokenInvalid(cstgIdentity, storedTokens, logInfo, _logWarn)) { storedTokens = null; } if (!storedTokens || Date.now() > storedTokens.latestToken.refresh_expires) { - const promise = clientSideTokenGenerator.generateTokenAndStore(config.apiBaseUrl, config.cstg, cstgIdentity, storageManager, _logInfo, _logWarn); - _logInfo('Generate token using CSTG'); + const promise = clientSideTokenGenerator.generateTokenAndStore(config.apiBaseUrl, config.cstg, cstgIdentity, storageManager, logInfo, _logWarn); + logInfo('Generate token using CSTG'); return { callback: (cb) => { promise.then((result) => { - _logInfo('Token generation responded, passing the new token on.', result); + logInfo('Token generation responded, passing the new token on.', result); cb(result); }); } }; @@ -741,25 +754,25 @@ export function Uid2GetId(config, prebidStorageManager, _logInfo, _logWarn) { const useSuppliedToken = !(storedTokens?.latestToken) || (suppliedToken && suppliedToken.identity_expires > storedTokens.latestToken.identity_expires); const newestAvailableToken = useSuppliedToken ? suppliedToken : storedTokens.latestToken; - _logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); + logInfo('UID2 module selected latest token', useSuppliedToken, newestAvailableToken); if ((!newestAvailableToken || Date.now() > newestAvailableToken.refresh_expires)) { - _logInfo('Newest available token is expired and not refreshable.'); + logInfo('Newest available token is expired and not refreshable.'); return { id: null }; } if (Date.now() > newestAvailableToken.identity_expires) { - const promise = refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, _logInfo, _logWarn); - _logInfo('Token is expired but can be refreshed, attempting refresh.'); + const promise = refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, logInfo, _logWarn); + logInfo('Token is expired but can be refreshed, attempting refresh.'); return { callback: (cb) => { promise.then((result) => { - _logInfo('Refresh reponded, passing the updated token on.', result); + logInfo('Refresh reponded, passing the updated token on.', result); cb(result); }); } }; } // If should refresh (but don't need to), refresh in the background. if (Date.now() > newestAvailableToken.refresh_from) { - _logInfo(`Refreshing token in background with low priority.`); - refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, _logInfo, _logWarn); + logInfo(`Refreshing token in background with low priority.`); + refreshTokenAndStore(config.apiBaseUrl, newestAvailableToken, config.clientId, storageManager, logInfo, _logWarn); } const tokens = { originalToken: suppliedToken ?? storedTokens?.originalToken, diff --git a/modules/undertoneBidAdapter.js b/modules/undertoneBidAdapter.js index c7e8102ffc9..1ea2f2c1ce6 100644 --- a/modules/undertoneBidAdapter.js +++ b/modules/undertoneBidAdapter.js @@ -2,7 +2,7 @@ * Adapter to send bids to Undertone */ -import {deepAccess, parseUrl} from '../src/utils.js'; +import {deepAccess, parseUrl, extractDomainFromHost} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; @@ -26,24 +26,6 @@ function getBidFloor(bidRequest, mediaType) { return (floor && floor.currency === 'USD' && floor.floor) || 0; } -function extractDomainFromHost(pageHost) { - let domain = null; - try { - let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); - if (domains != null && domains.length > 0) { - domain = domains[0]; - for (let i = 1; i < domains.length; i++) { - if (domains[i].length > domain.length) { - domain = domains[i]; - } - } - } - } catch (e) { - domain = null; - } - return domain; -} - function getGdprQueryParams(gdprConsent) { if (!gdprConsent) { return null; diff --git a/modules/unrulyBidAdapter.js b/modules/unrulyBidAdapter.js index b825003f36f..53e6629d8cc 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -31,7 +31,7 @@ const addBidFloorInfo = (validBid) => { currency: 'USD', mediaType: key, size: '*' - }).floor || 0; + })?.floor || 0; } else { floor = validBid.params.floor || 0; } @@ -226,7 +226,7 @@ export const adapter = { 'options': { 'contentType': 'application/json' }, - 'protectedAudienceEnabled': bidderRequest.fledgeEnabled + 'protectedAudienceEnabled': bidderRequest.paapi?.enabled }, validBidRequests, bidderRequest); }, @@ -261,7 +261,7 @@ export const adapter = { return { bids, - fledgeAuctionConfigs + paapi: fledgeAuctionConfigs }; } }; diff --git a/modules/userId/eids.js b/modules/userId/eids.js index e5f7e3b8fb2..57341773184 100644 --- a/modules/userId/eids.js +++ b/modules/userId/eids.js @@ -1,27 +1,30 @@ -import {deepAccess, deepClone, isFn, isPlainObject, isStr} from '../../src/utils.js'; +import {deepClone, isFn, isStr} from '../../src/utils.js'; + +/** + * @typedef {import('./index.js').SubmodulePriorityMap} SubmodulePriorityMap + */ export const EID_CONFIG = new Map(); // this function will create an eid object for the given UserId sub-module -function createEidObject(userIdData, subModuleKey) { - const conf = EID_CONFIG.get(subModuleKey); - if (conf && userIdData) { +function createEidObject(userIdData, subModuleKey, eidConf) { + if (eidConf && userIdData) { let eid = {}; - eid.source = isFn(conf['getSource']) ? conf['getSource'](userIdData) : conf['source']; - const value = isFn(conf['getValue']) ? conf['getValue'](userIdData) : userIdData; + eid.source = isFn(eidConf['getSource']) ? eidConf['getSource'](userIdData) : eidConf['source']; + const value = isFn(eidConf['getValue']) ? eidConf['getValue'](userIdData) : userIdData; if (isStr(value)) { - const uid = { id: value, atype: conf['atype'] }; + const uid = { id: value, atype: eidConf['atype'] }; // getUidExt - if (isFn(conf['getUidExt'])) { - const uidExt = conf['getUidExt'](userIdData); + if (isFn(eidConf['getUidExt'])) { + const uidExt = eidConf['getUidExt'](userIdData); if (uidExt) { uid.ext = uidExt; } } eid.uids = [uid]; // getEidExt - if (isFn(conf['getEidExt'])) { - const eidExt = conf['getEidExt'](userIdData); + if (isFn(eidConf['getEidExt'])) { + const eidExt = eidConf['getEidExt'](userIdData); if (eidExt) { eid.ext = eidExt; } @@ -32,7 +35,7 @@ function createEidObject(userIdData, subModuleKey) { return null; } -export function createEidsArray(bidRequestUserId) { +export function createEidsArray(bidRequestUserId, eidConfigs = EID_CONFIG) { const allEids = {}; function collect(eid) { const key = JSON.stringify([eid.source?.toLowerCase(), eid.ext]); @@ -45,31 +48,24 @@ export function createEidsArray(bidRequestUserId) { Object.entries(bidRequestUserId).forEach(([name, values]) => { values = Array.isArray(values) ? values : [values]; - const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name)); + const eids = name === 'pubProvidedId' ? deepClone(values) : values.map(value => createEidObject(value, name, eidConfigs.get(name))); eids.filter(eid => eid != null).forEach(collect); }) return Object.values(allEids); } /** - * @param {SubmoduleContainer[]} submodules + * @param {SubmodulePriorityMap} priorityMap */ -export function buildEidPermissions(submodules) { - let eidPermissions = []; - submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length) - .forEach(i => { - Object.keys(i.idObj).forEach(key => { - const eidConf = EID_CONFIG.get(key) || {}; - if (deepAccess(i, 'config.bidders') && Array.isArray(i.config.bidders) && - eidConf.source) { - eidPermissions.push( - { - source: eidConf.source, - bidders: i.config.bidders - } - ); - } - }); - }); - return eidPermissions; +export function getEids(priorityMap) { + const eidConfigs = new Map(); + const idValues = {}; + Object.entries(priorityMap).forEach(([key, submodules]) => { + const submodule = submodules.find(mod => mod.idObj?.[key] != null); + if (submodule) { + idValues[key] = submodule.idObj[key]; + eidConfigs.set(key, submodule.submodule.eids?.[key]) + } + }) + return createEidsArray(idValues, eidConfigs); } diff --git a/modules/userId/eids.md b/modules/userId/eids.md index c10ecde9c30..53567032175 100644 --- a/modules/userId/eids.md +++ b/modules/userId/eids.md @@ -81,14 +81,6 @@ userIdAsEids = [ }] }, - { - source: 'parrable.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }, - { source: 'liveramp.com', uids: [{ @@ -107,7 +99,7 @@ userIdAsEids = [ segments: ['s1', 's2'] } }, - + { source: 'bidswitch.net', uids: [{ @@ -118,7 +110,7 @@ userIdAsEids = [ } }] }, - + { source: 'liveintent.indexexchange.com', uids: [{ @@ -161,7 +153,7 @@ userIdAsEids = [ provider: 'liveintent.com' } }] - }, + }, { source: 'media.net', @@ -185,6 +177,17 @@ userIdAsEids = [ }] }, + { + source: 'fpid.liveintent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1, + ext: { + provider: 'liveintent.com' + } + }] + }, + { source: 'merkleinc.com', uids: [{ diff --git a/modules/userId/index.js b/modules/userId/index.js index 256415b153b..f1417bb20c7 100644 --- a/modules/userId/index.js +++ b/modules/userId/index.js @@ -4,63 +4,47 @@ */ /** - * @interface Submodule + * @typedef Submodule + * @property {string} name - used to link submodule with config + * @property {decode} decode + * @property {getId} getId + * @property {Object} eids + * @property {number} [gvlid] - vendor ID + * @property {extendId} [extendId] + * @property {function} [domainOverride] - use a predefined domain override for cookies or provide your own + * @property {function(): string} [findRootDomain] - returns the root domain */ /** - * @function - * @summary performs action to obtain id and return a value in the callback's response argument. - * If IdResponse#id is defined, then it will be written to the current active storage. - * 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 + * Performs action to obtain id and return a value in the callback's response argument. + * If IdResponse#id is defined, then it will be written to the current active storage. + * If IdResponse#callback is defined, then it'll called at the end of auction. + * It's permissible to return neither, one, or both fields. + * @callback getId * @param {SubmoduleConfig} config - * @param {ConsentData|undefined} consentData - * @param {(Object|undefined)} cacheIdObj - * @return {(IdResponse|undefined)} A response object that contains id and/or callback. + * @param {ConsentData|undefined} [consentData] + * @param {Object|undefined} [cacheIdObj] + * @returns {IdResponse|undefined} A response object that contains id and/or callback. */ /** - * @function - * @summary Similar to Submodule#getId, this optional method returns response to for id that exists already. - * If IdResponse#id is defined, then it will be written to the current active storage even if it exists already. - * 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 + * Similar to `getId`, this optional method returns response to for id that exists already. + * If IdResponse#id is defined, then it will be written to the current active storage even if it exists already. + * If IdResponse#callback is defined, then it'll called at the end of auction. + * It's permissible to return neither, one, or both fields. + * @callback extendId * @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. + * @returns {IdResponse|function(callback:function)} A response object that contains id and/or callback. */ /** - * @function - * @summary decode a stored value for passing to bid requests - * @name Submodule#decode + * Decode a stored value for passing to bid requests + * @callback decode * @param {Object|string} value - * @param {SubmoduleConfig|undefined} config - * @return {(Object|undefined)} - */ - -/** - * @property - * @summary used to link submodule with config - * @name Submodule#name - * @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} + * @param {SubmoduleConfig|undefined} [config] + * @returns {Object|undefined} */ /** @@ -69,6 +53,7 @@ * @property {(SubmoduleStorage|undefined)} storage - browser storage config * @property {(SubmoduleParams|undefined)} params - params config for use by the submodule.getId function * @property {(Object|undefined)} value - if not empty, this value is added to bid requests for access in adapters + * @property {string[]} [enabledStorageTypes] */ /** @@ -111,6 +96,7 @@ * @property {(Object|undefined)} idObj - cache decoded id value (this is copied to every adUnit bid) * @property {(function|undefined)} callback - holds reference to submodule.getId() result if it returned a function. Will be set to undefined after callback executes * @property {StorageManager} storageMgr + * @property {string[]} [enabledStorageTypes] */ /** @@ -122,18 +108,22 @@ /** * @typedef {Object} IdResponse - * @property {(Object|undefined)} id - id data - * @property {(function|undefined)} callback - function that will return an id + * @property {Object} [id] - id data + * @property {function} [callback] - function that will return an id */ -import {find, includes} from '../../src/polyfill.js'; +/** + * @typedef {{[idKey: string]: SubmoduleContainer[]}} SubmodulePriorityMap + */ + +import {find} from '../../src/polyfill.js'; import {config} from '../../src/config.js'; import * as events from '../../src/events.js'; import {getGlobal} from '../../src/prebidGlobal.js'; import adapterManager, {gdprDataHandler} from '../../src/adapterManager.js'; -import { EVENTS } from '../../src/constants.js'; +import {EVENTS} from '../../src/constants.js'; import {module, ready as hooksReady} from '../../src/hook.js'; -import {buildEidPermissions, createEidsArray, EID_CONFIG} from './eids.js'; +import {EID_CONFIG, getEids} from './eids.js'; import { getCoreStorageManager, getStorageManager, @@ -144,10 +134,8 @@ import { deepAccess, deepSetValue, delayExecution, - getPrebidInternal, isArray, isEmpty, - isEmptyStr, isFn, isGptPubadsDefined, isNumber, @@ -158,7 +146,6 @@ import { } from '../../src/utils.js'; import {getPPID as coreGetPPID} from '../../src/adserver.js'; import {defer, GreedyPromise} from '../../src/utils/promise.js'; -import {registerOrtbProcessor, REQUEST} from '../../src/pbjsORTB.js'; import {newMetrics, timedAuctionHook, useMetrics} from '../../src/utils/perfMetrics.js'; import {findRootDomain} from '../../src/fpd/rootDomain.js'; import {allConsent, GDPR_GVLIDS} from '../../src/consentHandler.js'; @@ -166,25 +153,22 @@ import {MODULE_TYPE_UID} from '../../src/activities/modules.js'; import {isActivityAllowed} from '../../src/activities/rules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../src/activities/activities.js'; import {activityParams} from '../../src/activities/activityParams.js'; +import {USERSYNC_DEFAULT_CONFIG} from '../../src/userSync.js'; +import {startAuction} from '../../src/prebid.js'; const MODULE_NAME = 'User ID'; const COOKIE = STORAGE_TYPE_COOKIES; const LOCAL_STORAGE = STORAGE_TYPE_LOCALSTORAGE; -const DEFAULT_SYNC_DELAY = 500; -const NO_AUCTION_DELAY = 0; export const PBJS_USER_ID_OPTOUT_NAME = '_pbjs_id_optout'; export const coreStorage = getCoreStorageManager('userId'); export const dep = { isAllowed: isActivityAllowed } -/** @type {boolean} */ -let addedUserIdHook = false; - /** @type {SubmoduleContainer[]} */ let submodules = []; -/** @type {SubmoduleContainer[]} */ +/** @type {PriorityMaps} */ let initializedSubmodules; /** @type {SubmoduleConfig[]} */ @@ -239,6 +223,29 @@ function cookieSetter(submodule, storageMgr) { } } +function setValueInCookie(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const setCookie = cookieSetter(submodule); + + setCookie(null, valueStr, expiresStr); + setCookie('_cst', getConsentHash(), expiresStr); + if (typeof storage.refreshInSeconds === 'number') { + setCookie('_last', new Date().toUTCString(), expiresStr); + } +} + +function setValueInLocalStorage(submodule, valueStr, expiresStr) { + const storage = submodule.config.storage; + const mgr = submodule.storageMgr; + + mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); + mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); + mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); + if (typeof storage.refreshInSeconds === 'number') { + mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + } +} + /** * @param {SubmoduleContainer} submodule * @param {(Object|string)} value @@ -248,60 +255,78 @@ export function setStoredValue(submodule, value) { * @type {SubmoduleStorage} */ const storage = submodule.config.storage; - const mgr = submodule.storageMgr; try { const expiresStr = (new Date(Date.now() + (storage.expires * (60 * 60 * 24 * 1000)))).toUTCString(); const valueStr = isPlainObject(value) ? JSON.stringify(value) : value; - if (storage.type === COOKIE) { - const setCookie = cookieSetter(submodule); - setCookie(null, valueStr, expiresStr); - setCookie('_cst', getConsentHash(), expiresStr); - if (typeof storage.refreshInSeconds === 'number') { - setCookie('_last', new Date().toUTCString(), expiresStr); - } - } else if (storage.type === LOCAL_STORAGE) { - mgr.setDataInLocalStorage(`${storage.name}_exp`, expiresStr); - mgr.setDataInLocalStorage(`${storage.name}_cst`, getConsentHash()); - mgr.setDataInLocalStorage(storage.name, encodeURIComponent(valueStr)); - if (typeof storage.refreshInSeconds === 'number') { - mgr.setDataInLocalStorage(`${storage.name}_last`, new Date().toUTCString()); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + setValueInCookie(submodule, valueStr, expiresStr); + break; + case LOCAL_STORAGE: + setValueInLocalStorage(submodule, valueStr, expiresStr); + break; } - } + }); } catch (error) { logError(error); } } +function deleteValueFromCookie(submodule) { + const setCookie = cookieSetter(submodule, coreStorage); + const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); + + ['', '_last', '_cst'].forEach(suffix => { + try { + setCookie(suffix, '', expiry); + } catch (e) { + logError(e); + } + }) +} + +function deleteValueFromLocalStorage(submodule) { + ['', '_last', '_exp', '_cst'].forEach(suffix => { + try { + coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix); + } catch (e) { + logError(e); + } + }); +} + export function deleteStoredValue(submodule) { - let deleter, suffixes; - switch (submodule.config?.storage?.type) { - case COOKIE: - const setCookie = cookieSetter(submodule, coreStorage); - const expiry = (new Date(Date.now() - 1000 * 60 * 60 * 24)).toUTCString(); - deleter = (suffix) => setCookie(suffix, '', expiry) - suffixes = ['', '_last', '_cst']; - break; - case LOCAL_STORAGE: - deleter = (suffix) => coreStorage.removeDataFromLocalStorage(submodule.config.storage.name + suffix) - suffixes = ['', '_last', '_exp', '_cst']; - break; - } - if (deleter) { - suffixes.forEach(suffix => { - try { - deleter(suffix) - } catch (e) { - logError(e); - } - }); - } + populateEnabledStorageTypes(submodule); + + submodule.enabledStorageTypes.forEach(storageType => { + switch (storageType) { + case COOKIE: + deleteValueFromCookie(submodule); + break; + case LOCAL_STORAGE: + deleteValueFromLocalStorage(submodule); + break; + } + }); } -function setPrebidServerEidPermissions(initializedSubmodules) { - let setEidPermissions = getPrebidInternal().setEidPermissions; - if (typeof setEidPermissions === 'function' && isArray(initializedSubmodules)) { - setEidPermissions(buildEidPermissions(initializedSubmodules)); +function getValueFromCookie(submodule, storedKey) { + return submodule.storageMgr.getCookie(storedKey) +} + +function getValueFromLocalStorage(submodule, storedKey) { + const mgr = submodule.storageMgr; + const storage = submodule.config.storage; + const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); + + // empty string means no expiration set + if (storedValueExp === '') { + return mgr.getDataFromLocalStorage(storedKey); + } else if (storedValueExp && ((new Date(storedValueExp)).getTime() - Date.now() > 0)) { + return decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); } } @@ -311,24 +336,23 @@ function setPrebidServerEidPermissions(initializedSubmodules) { * @returns {string} */ function getStoredValue(submodule, key = undefined) { - const mgr = submodule.storageMgr; const storage = submodule.config.storage; const storedKey = key ? `${storage.name}_${key}` : storage.name; let storedValue; try { - if (storage.type === COOKIE) { - storedValue = mgr.getCookie(storedKey); - } else if (storage.type === LOCAL_STORAGE) { - const storedValueExp = mgr.getDataFromLocalStorage(`${storage.name}_exp`); - // empty string means no expiration set - if (storedValueExp === '') { - storedValue = mgr.getDataFromLocalStorage(storedKey); - } else if (storedValueExp) { - if ((new Date(storedValueExp)).getTime() - Date.now() > 0) { - storedValue = decodeURIComponent(mgr.getDataFromLocalStorage(storedKey)); - } + submodule.enabledStorageTypes.find(storageType => { + switch (storageType) { + case COOKIE: + storedValue = getValueFromCookie(submodule, storedKey); + break; + case LOCAL_STORAGE: + storedValue = getValueFromLocalStorage(submodule, storedKey); + break; } - } + + return !!storedValue; + }); + // support storing a string or a stringified object if (typeof storedValue === 'string' && storedValue.trim().charAt(0) === '{') { storedValue = JSON.parse(storedValue); @@ -342,8 +366,9 @@ function getStoredValue(submodule, key = undefined) { /** * @param {SubmoduleContainer[]} submodules * @param {function} cb - callback for after processing is done. + * @param {PriorityMaps} priorityMaps */ -function processSubmoduleCallbacks(submodules, cb, allModules) { +function processSubmoduleCallbacks(submodules, cb, priorityMaps) { cb = uidMetrics().fork().startTiming('userId.callbacks.total').stopBefore(cb); const done = delayExecution(() => { clearTimeout(timeoutID); @@ -359,7 +384,8 @@ function processSubmoduleCallbacks(submodules, cb, allModules) { } // cache decoded value (this is copied to every adUnit bid) submodule.idObj = submodule.submodule.decode(idObj, submodule.config); - updatePPID(getCombinedSubmoduleIds(allModules)); + priorityMaps.refresh(); + updatePPID(priorityMaps); } else { logInfo(`${MODULE_NAME}: ${submodule.submodule.name} - request id responded with an empty value`); } @@ -377,95 +403,156 @@ function processSubmoduleCallbacks(submodules, cb, allModules) { } /** - * This function will create a combined object for all subModule Ids - * @param {SubmoduleContainer[]} submodules + * @param {SubmodulePriorityMap} priorityMap + * @returns {{}} */ -function getCombinedSubmoduleIds(submodules) { - if (!Array.isArray(submodules) || !submodules.length) { - return {}; - } - return getPrioritizedCombinedSubmoduleIds(submodules) +function getIds(priorityMap) { + return Object.fromEntries( + Object.entries(priorityMap) + .map(([key, submodules]) => [key, submodules.find(mod => mod.idObj?.[key] != null)?.idObj?.[key]]) + .filter(([_, value]) => value != null) + ) } -/** - * This function will return a submodule ID object for particular source name - * @param {SubmoduleContainer[]} submodules - * @param {string} sourceName - */ -function getSubmoduleId(submodules, sourceName) { - if (!Array.isArray(submodules) || !submodules.length) { - return {}; +function getPrimaryIds(submodule) { + if (submodule.primaryIds) return submodule.primaryIds; + const ids = Object.keys(submodule.eids ?? {}); + if (ids.length > 1) { + throw new Error(`ID submodule ${submodule.name} can provide multiple IDs, but does not specify 'primaryIds'`) } - - const prioritisedIds = getPrioritizedCombinedSubmoduleIds(submodules); - const eligibleIdName = Object.keys(prioritisedIds).find(idName => { - const config = EID_CONFIG.get(idName); - return config?.source === sourceName || (isFn(config?.getSource) && config.getSource() === sourceName); - }); - - return eligibleIdName ? {[eligibleIdName]: prioritisedIds[eligibleIdName]} : []; + return ids; } /** - * This function will create a combined object for bidder with allowed subModule Ids - * @param {SubmoduleContainer[]} submodules - * @param {string} bidder + * Given a collection of items, where each item maps to any number of IDs (getKeys) and an ID module (getIdMod), + * return a map from ID key to all items that map to that ID key, in order of priority (highest priority first). + * + * @template T + * @param {T[]} items + * @param {(item: T) => string[]} getKeys + * @param {(item: T) => Submodule} getIdMod + * @returns {{[key: string]: T[]}} */ -function getCombinedSubmoduleIdsForBidder(submodules, bidder) { - if (!Array.isArray(submodules) || !submodules.length || !bidder) { - return {}; - } - const eligibleSubmodules = submodules - .filter(i => !i.config.bidders || !isArray(i.config.bidders) || includes(i.config.bidders, bidder)) - - return getPrioritizedCombinedSubmoduleIds(eligibleSubmodules); -} - -function collectByPriority(submodules, getIds, getName) { - return Object.fromEntries(Object.entries(submodules.reduce((carry, submod) => { - const ids = getIds(submod); - ids && Object.keys(ids).forEach(key => { - const maybeCurrentIdPriority = idPriority[key]?.indexOf(getName(submod)); - const currentIdPriority = isNumber(maybeCurrentIdPriority) ? maybeCurrentIdPriority : -1; - const currentIdState = {priority: currentIdPriority, value: ids[key]}; - if (carry[key]) { - const winnerIdState = currentIdState.priority > carry[key].priority ? currentIdState : carry[key]; - carry[key] = winnerIdState; - } else { - carry[key] = currentIdState; - } - }); - return carry; - }, {})).map(([k, v]) => [k, v.value])); +function orderByPriority(items, getKeys, getIdMod) { + const tally = {}; + items.forEach(item => { + const module = getIdMod(item); + const primaryIds = getPrimaryIds(module); + getKeys(item).forEach(key => { + const keyItems = tally[key] = tally[key] ?? [] + const keyPriority = idPriority[key]?.indexOf(module.name) ?? (primaryIds.includes(key) ? 0 : -1); + const pos = keyItems.findIndex(([priority]) => priority < keyPriority); + keyItems.splice(pos === -1 ? keyItems.length : pos, 0, [keyPriority, item]) + }) + }) + return Object.fromEntries(Object.entries(tally).map(([key, items]) => [key, items.map(([_, item]) => item)])) } /** - * @param {SubmoduleContainer[]} submodules + * @typedef {Object} PriorityMaps + * @property {SubmoduleContainer[]} submodules all active submodules + * @property {SubmodulePriorityMap} global priority map for global (not bidder-specific) submodules + * @property {SubmodulePriorityMap} combined priority map for ALL submodules, disregarding bidder filters + * @property {{[bidder: string]: SubmodulePriorityMap}} bidder priority maps for each bidder's specific submodules + * @property {(submodules: SubmoduleContainer[]) => void} refresh refresh priority maps, optionally adding or updating some submodules. + * Should be called every time a submodule's ID is updated. */ -function getPrioritizedCombinedSubmoduleIds(submodules) { - return collectByPriority( - submodules.filter(i => isPlainObject(i.idObj) && Object.keys(i.idObj).length), - (submod) => submod.idObj, - (submod) => submod.submodule.name - ) -} /** - * @param {AdUnit[]} adUnits - * @param {SubmoduleContainer[]} submodules + * @returns PriorityMaps */ -function addIdDataToAdUnitBids(adUnits, submodules) { +function mkPriorityMaps() { + const map = { + submodules: [], + global: {}, + bidder: {}, + combined: {}, + /** + * @param {SubmoduleContainer[]} addtlModules + */ + refresh(addtlModules = []) { + const refreshing = new Set(addtlModules.map(mod => mod.submodule)); + map.submodules = map.submodules.filter((mod) => !refreshing.has(mod.submodule)).concat(addtlModules); + update(); + } + } + function update() { + const modulesById = orderByPriority( + map.submodules, + (submod) => Object.keys(submod.idObj ?? {}), + (submod) => submod.submodule, + ) + const global = {}; + const bidder = {}; + Object.entries(modulesById) + .forEach(([key, modules]) => { + let allNonGlobal = true; + const bidderFilters = new Set(); + modules.map(mod => mod.config.bidders) + .forEach(bidders => { + if (Array.isArray(bidders) && bidders.length > 0) { + bidders.forEach(bidder => bidderFilters.add(bidder)); + } else { + allNonGlobal = false; + } + }) + if (bidderFilters.size > 0 && !allNonGlobal) { + logWarn(`userID modules ${modules.map(mod => mod.submodule.name).join(', ')} provide the same ID ('${key}'), but are configured for different bidders. ID will be skipped.`) + } else { + if (bidderFilters.size === 0) { + global[key] = modules; + } else { + bidderFilters.forEach(bidderCode => { + bidder[bidderCode] = bidder[bidderCode] ?? {}; + bidder[bidderCode][key] = modules; + }) + } + } + }); + const combined = Object.values(bidder).concat([global]).reduce((combo, map) => Object.assign(combo, map), {}); + Object.assign(map, {global, bidder, combined}); + } + return map; +} + +export function enrichEids(ortb2Fragments) { + const {global: globalFpd, bidder: bidderFpd} = ortb2Fragments; + const {global: globalMods, bidder: bidderMods} = initializedSubmodules; + const globalEids = getEids(globalMods); + if (globalEids.length > 0) { + deepSetValue(globalFpd, 'user.ext.eids', (globalFpd.user?.ext?.eids ?? []).concat(globalEids)); + } + Object.entries(bidderMods).forEach(([bidder, moduleMap]) => { + const bidderEids = getEids(moduleMap); + if (bidderEids.length > 0) { + deepSetValue( + bidderFpd, + `${bidder}.user.ext.eids`, + (bidderFpd[bidder]?.user?.ext?.eids ?? []).concat(bidderEids) + ); + } + }) + return ortb2Fragments; +} + +function addIdData({adUnits, ortb2Fragments}) { + ortb2Fragments = ortb2Fragments ?? {global: {}, bidder: {}} + enrichEids(ortb2Fragments); if ([adUnits].some(i => !Array.isArray(i) || !i.length)) { return; } + const globalIds = getIds(initializedSubmodules.global); + const globalEids = ortb2Fragments.global.user?.ext?.eids || []; adUnits.forEach(adUnit => { if (adUnit.bids && isArray(adUnit.bids)) { adUnit.bids.forEach(bid => { - 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); + const bidderIds = Object.assign({}, globalIds, getIds(initializedSubmodules.bidder[bid.bidder] ?? {})); + const bidderEids = globalEids.concat(ortb2Fragments.bidder[bid.bidder]?.user?.ext?.eids || []); + if (Object.keys(bidderIds).length > 0) { + bid.userId = bidderIds; + } + if (bidderEids.length > 0) { + bid.userIdAsEids = bidderEids; } }); } @@ -518,7 +605,7 @@ function idSystemInitializer({delay = GreedyPromise.timeout} = {}) { })) .then(() => startCallbacks.promise.finally(initMetrics.startTiming('userId.callbacks.pending'))) .then(checkRefs(() => { - const modWithCb = initModules.filter(item => isFn(item.callback)); + const modWithCb = initModules.submodules.filter(item => isFn(item.callback)); if (modWithCb.length) { return new GreedyPromise((resolve) => processSubmoduleCallbacks(modWithCb, resolve, initModules)); } @@ -591,25 +678,43 @@ function getPPID(eids = getUserIdsAsEids() || []) { * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. * @param {function} fn required; The next function in the chain, used by hook.js */ -export const requestBidsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { +export const startAuctionHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj, {delay = GreedyPromise.timeout, getIds = getUserIdsAsync} = {}) { GreedyPromise.race([ getIds().catch(() => null), delay(auctionDelay) ]).then(() => { - // pass available user id data to bid adapters - addIdDataToAdUnitBids(reqBidsConfigObj.adUnits || getGlobal().adUnits, initializedSubmodules); + addIdData(reqBidsConfigObj); uidMetrics().join(useMetrics(reqBidsConfigObj.metrics), {propagate: false, includeGroups: true}); // calling fn allows prebid to continue processing fn.call(this, reqBidsConfigObj); }); }); +/** + * Append user id data from config to bids to be accessed in adapters when there are no submodules. + * @param {function} fn required; The next function in the chain, used by hook.js + * @param {Object} reqBidsConfigObj required; This is the same param that's used in pbjs.requestBids. + */ +export const addUserIdsHook = timedAuctionHook('userId', function requestBidsHook(fn, reqBidsConfigObj) { + addIdData(reqBidsConfigObj); + // calling fn allows prebid to continue processing + fn.call(this, reqBidsConfigObj); +}); + +/** + * Is startAuctionHook added + * @returns {boolean} + */ +function addedStartAuctionHook() { + return !!startAuction.getHooks({hook: startAuctionHook}).length; +} + /** * This function will be exposed in global-name-space so that userIds stored by Prebid UserId module can be used by external codes as well. * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIds() { - return getCombinedSubmoduleIds(initializedSubmodules) + return getIds(initializedSubmodules.combined) } /** @@ -617,7 +722,7 @@ function getUserIds() { * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIdsAsEids() { - return createEidsArray(getUserIds()) + return getEids(initializedSubmodules.combined) } /** @@ -626,7 +731,7 @@ function getUserIdsAsEids() { */ function getUserIdsAsEidBySource(sourceName) { - return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; + return getUserIdsAsEids().filter(eid => eid.source === sourceName)[0]; } /** @@ -634,7 +739,7 @@ function getUserIdsAsEidBySource(sourceName) { * Sample use case is exposing this function to ESP */ function getEncryptedEidsForSource(source, encrypt, customFunction) { - return initIdSystem().then(() => { + return retryOnCancel().then(() => { let eidsSignals = {}; if (isFn(customFunction)) { @@ -693,6 +798,23 @@ function registerSignalSources() { } } +function retryOnCancel(initParams) { + return initIdSystem(initParams).then( + () => getUserIds(), + (e) => { + if (e === INIT_CANCELED) { + // there's a pending refresh - because GreedyPromise runs this synchronously, we are now in the middle + // of canceling the previous init, before the refresh logic has had a chance to run. + // Use a "normal" Promise to clear the stack and let it complete (or this will just recurse infinitely) + return Promise.resolve().then(getUserIdsAsync) + } else { + logError('Error initializing userId', e) + return GreedyPromise.reject(e) + } + } + ); +} + /** * Force (re)initialization of ID submodules. * @@ -704,12 +826,12 @@ function registerSignalSources() { * @param callback? called when the refresh is complete */ function refreshUserIds({submoduleNames} = {}, callback) { - return initIdSystem({refresh: true, submoduleNames}) - .then(() => { + return retryOnCancel({refresh: true, submoduleNames}) + .then((userIds) => { if (callback && isFn(callback)) { callback(); } - return getUserIds(); + return userIds; }); } @@ -725,20 +847,7 @@ function refreshUserIds({submoduleNames} = {}, callback) { */ function getUserIdsAsync() { - return initIdSystem().then( - () => getUserIds(), - (e) => { - if (e === INIT_CANCELED) { - // there's a pending refresh - because GreedyPromise runs this synchronously, we are now in the middle - // of canceling the previous init, before the refresh logic has had a chance to run. - // Use a "normal" Promise to clear the stack and let it complete (or this will just recurse infinitely) - return Promise.resolve().then(getUserIdsAsync) - } else { - logError('Error initializing userId', e) - return GreedyPromise.reject(e) - } - } - ); + return retryOnCancel(); } export function getConsentHash() { @@ -757,7 +866,7 @@ function consentChanged(submodule) { return !storedConsent || storedConsent !== getConsentHash(); } -function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { +function populateSubmoduleId(submodule, forceRefresh) { // TODO: the ID submodule API only takes GDPR consent; it should be updated now that GDPR // is only a tiny fraction of a vast consent universe const gdprConsent = gdprDataHandler.getConsentData(); @@ -776,8 +885,10 @@ function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { } if (!storedId || refreshNeeded || forceRefresh || consentChanged(submodule)) { + const extendedConfig = Object.assign({ enabledStorageTypes: submodule.enabledStorageTypes }, submodule.config); + // 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, gdprConsent, storedId); + response = submodule.submodule.getId(extendedConfig, gdprConsent, 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, gdprConsent, storedId); @@ -810,12 +921,12 @@ function populateSubmoduleId(submodule, forceRefresh, allSubmodules) { if (response.id) { submodule.idObj = submodule.submodule.decode(response.id, submodule.config); } } } - updatePPID(getCombinedSubmoduleIds(allSubmodules)); } -function updatePPID(userIds = getUserIds()) { - if (userIds && ppidSource) { - const ppid = getPPID(createEidsArray(userIds)); +function updatePPID(priorityMaps) { + const eids = getEids(priorityMaps.combined); + if (eids.length && ppidSource) { + const ppid = getPPID(eids); if (ppid) { if (isGptPubadsDefined()) { window.googletag.pubads().setPublisherProvidedId(ppid); @@ -830,10 +941,12 @@ function updatePPID(userIds = getUserIds()) { } } -function initSubmodules(dest, submodules, forceRefresh = false) { +function initSubmodules(priorityMaps, submodules, forceRefresh = false) { return uidMetrics().fork().measureTime('userId.init.modules', function () { if (!submodules.length) return []; // to simplify log messages from here on + submodules.forEach(submod => populateEnabledStorageTypes(submod)); + /** * filter out submodules that: * @@ -853,7 +966,7 @@ function initSubmodules(dest, submodules, forceRefresh = false) { const initialized = submodules.reduce((carry, submodule) => { return submoduleMetrics(submodule.submodule.name).measureTime('init', () => { try { - populateSubmoduleId(submodule, forceRefresh, submodules); + populateSubmoduleId(submodule, forceRefresh); carry.push(submodule); } catch (e) { logError(`Error in userID module '${submodule.submodule.name}':`, e); @@ -861,27 +974,20 @@ function initSubmodules(dest, submodules, forceRefresh = false) { return carry; }) }, []); - if (initialized.length) { - setPrebidServerEidPermissions(initialized); - } - initialized.forEach(updateInitializedSubmodules.bind(null, dest)); + priorityMaps.refresh(initialized); + updatePPID(priorityMaps); return initialized; }) } -function updateInitializedSubmodules(dest, submodule) { - let updated = false; - for (let i = 0; i < dest.length; i++) { - if (submodule.config.name.toLowerCase() === dest[i].config.name.toLowerCase()) { - updated = true; - dest[i] = submodule; - break; - } - } +function getConfiguredStorageTypes(config) { + return config?.storage?.type?.trim().split(/\s*&\s*/) || []; +} - if (!updated) { - dest.push(submodule); - } +function hasValidStorageTypes(config) { + const storageTypes = getConfiguredStorageTypes(config); + + return storageTypes.every(storageType => ALL_STORAGE_TYPES.has(storageType)); } /** @@ -891,64 +997,102 @@ function updateInitializedSubmodules(dest, submodule) { * @param {SubmoduleConfig[]} configRegistry * @returns {SubmoduleConfig[]} */ -function getValidSubmoduleConfigs(configRegistry) { +export function getValidSubmoduleConfigs(configRegistry) { + function err(msg, ...args) { + logWarn(`Invalid userSync.userId config: ${msg}`, ...args) + } if (!Array.isArray(configRegistry)) { + if (configRegistry != null) { + err('must be an array', configRegistry); + } return []; } - return configRegistry.reduce((carry, config) => { - // every submodule config obj must contain a valid 'name' - if (!config || isEmptyStr(config.name)) { - return carry; - } - // Validate storage config contains 'type' and 'name' properties with non-empty string values - // 'type' must be one of html5, cookies - if (config.storage && - !isEmptyStr(config.storage.type) && - !isEmptyStr(config.storage.name) && - ALL_STORAGE_TYPES.has(config.storage.type)) { - carry.push(config); - } else if (isPlainObject(config.value)) { - carry.push(config); - } else if (!config.storage && !config.value) { - carry.push(config); + return configRegistry.filter(config => { + if (!config?.name) { + return err('must specify "name"', config); + } else if (config.storage) { + if (!config.storage.name || !config.storage.type) { + return err('must specify "storage.name" and "storage.type"', config); + } else if (!hasValidStorageTypes(config)) { + return err('invalid "storage.type"', config) + } + ['expires', 'refreshInSeconds'].forEach(param => { + let value = config.storage[param]; + if (value != null && typeof value !== 'number') { + value = Number(value) + if (isNaN(value)) { + err(`storage.${param} must be a number and will be ignored`, config); + delete config.storage[param]; + } else { + config.storage[param] = value; + } + } + }); } - return carry; - }, []); + return true; + }) } const ALL_STORAGE_TYPES = new Set([LOCAL_STORAGE, COOKIE]); -function canUseStorage(submodule) { - switch (submodule.config?.storage?.type) { - case LOCAL_STORAGE: - if (submodule.storageMgr.localStorageIsEnabled()) { - if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); - return false - } - return true; - } - break; - case COOKIE: - if (submodule.storageMgr.cookiesAreEnabled()) { - if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { - logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); - return false; - } - return true - } - break; +function canUseLocalStorage(submodule) { + if (!submodule.storageMgr.localStorageIsEnabled()) { + return false; + } + + if (coreStorage.getDataFromLocalStorage(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out localStorage found, storage disabled`); + return false + } + + return true; +} + +function canUseCookies(submodule) { + if (!submodule.storageMgr.cookiesAreEnabled()) { + return false; + } + + if (coreStorage.getCookie(PBJS_USER_ID_OPTOUT_NAME)) { + logInfo(`${MODULE_NAME} - opt-out cookie found, storage disabled`); + return false; + } + + return true +} + +function populateEnabledStorageTypes(submodule) { + if (submodule.enabledStorageTypes) { + return; } - return false; + + const storageTypes = getConfiguredStorageTypes(submodule.config); + + submodule.enabledStorageTypes = storageTypes.filter(type => { + switch (type) { + case LOCAL_STORAGE: + return canUseLocalStorage(submodule); + case COOKIE: + return canUseCookies(submodule); + } + + return false; + }); +} + +function canUseStorage(submodule) { + return !!submodule.enabledStorageTypes.length; } function updateEIDConfig(submodules) { EID_CONFIG.clear(); - Object.entries(collectByPriority( - submodules, - (mod) => mod.eids, - (mod) => mod.name - )).forEach(([id, conf]) => EID_CONFIG.set(id, conf)); + Object.entries( + orderByPriority( + submodules, + (mod) => Object.keys(mod.eids || {}), + (mod) => mod + ) + ).forEach(([key, submodules]) => EID_CONFIG.set(key, submodules[0].eids[key])) } /** @@ -971,7 +1115,6 @@ function updateSubmodules() { 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, @@ -982,25 +1125,26 @@ function updateSubmodules() { }).filter(submodule => submodule !== null) .forEach((sm) => submodules.push(sm)); - if (!addedUserIdHook && submodules.length) { - // priority value 40 will load after consentManagement with a priority of 50 - getGlobal().requestBids.before(requestBidsHook, 40); - adapterManager.callDataDeletionRequest.before(requestDataDeletion); - coreGetPPID.after((next) => next(getPPID())); + if (submodules.length) { + if (!addedStartAuctionHook()) { + startAuction.getHooks({hook: addUserIdsHook}).remove(); + startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd + adapterManager.callDataDeletionRequest.before(requestDataDeletion); + coreGetPPID.after((next) => next(getPPID())); + } logInfo(`${MODULE_NAME} - usersync config updated for ${submodules.length} submodules: `, submodules.map(a => a.submodule.name)); - addedUserIdHook = true; } } /** * This function will update the idPriority according to the provided configuration * @param {Object} idPriorityConfig - * @param {SubmoduleContainer[]} submodules + * @param {Submodule[]} submodules */ function updateIdPriority(idPriorityConfig, submodules) { if (idPriorityConfig) { const result = {}; - const aliasToName = new Map(submodules.map(s => s.submodule.aliasName ? [s.submodule.aliasName, s.submodule.name] : [])); + const aliasToName = new Map(submodules.map(s => s.aliasName ? [s.aliasName, s.name] : [])); Object.keys(idPriorityConfig).forEach(key => { const priority = isArray(idPriorityConfig[key]) ? [...idPriorityConfig[key]].reverse() : [] result[key] = priority.map(s => aliasToName.has(s) ? aliasToName.get(s) : s); @@ -1009,6 +1153,8 @@ function updateIdPriority(idPriorityConfig, submodules) { } else { idPriority = {}; } + initializedSubmodules.refresh(); + updateEIDConfig(submodules) } export function requestDataDeletion(next, ...args) { @@ -1031,6 +1177,7 @@ export function requestDataDeletion(next, ...args) { * @param {Submodule} submodule */ export function attachIdSystem(submodule) { + submodule.findRootDomain = findRootDomain; if (!find(submoduleRegistry, i => i.name === submodule.name)) { submoduleRegistry.push(submodule); GDPR_GVLIDS.register(MODULE_TYPE_UID, submodule.name, submodule.gvlid) @@ -1066,8 +1213,7 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { ppidSource = undefined; submodules = []; configRegistry = []; - addedUserIdHook = false; - initializedSubmodules = []; + initializedSubmodules = mkPriorityMaps(); initIdSystem = idSystemInitializer({delay}); if (configListener != null) { configListener(); @@ -1082,10 +1228,10 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { ppidSource = userSync.ppid; if (userSync.userIds) { configRegistry = userSync.userIds; - syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : DEFAULT_SYNC_DELAY; - auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : NO_AUCTION_DELAY; + syncDelay = isNumber(userSync.syncDelay) ? userSync.syncDelay : USERSYNC_DEFAULT_CONFIG.syncDelay + auctionDelay = isNumber(userSync.auctionDelay) ? userSync.auctionDelay : USERSYNC_DEFAULT_CONFIG.auctionDelay; updateSubmodules(); - updateIdPriority(userSync.idPriority, submodules); + updateIdPriority(userSync.idPriority, submoduleRegistry); initIdSystem({ready: true}); } } @@ -1099,17 +1245,13 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { (getGlobal()).refreshUserIds = normalizePromise(refreshUserIds, true, 'refreshUserIds'); (getGlobal()).getUserIdsAsync = normalizePromise(getUserIdsAsync); (getGlobal()).getUserIdsAsEidBySource = getUserIdsAsEidBySource; + if (!addedStartAuctionHook()) { + // Add ortb2.user.ext.eids even if 0 submodules are added + startAuction.before(addUserIdsHook, 100); // use higher priority than dataController / rtd + } } // init config update listener to start the application init(config); module('userId', attachIdSystem, { postInstallAllowed: true }); - -export function setOrtbUserExtEids(ortbRequest, bidderRequest, context) { - const eids = deepAccess(context, 'bidRequests.0.userIdAsEids'); - if (eids && Object.keys(eids).length > 0) { - deepSetValue(ortbRequest, 'user.ext.eids', eids); - } -} -registerOrtbProcessor({type: REQUEST, name: 'userExtEids', fn: setOrtbUserExtEids}); diff --git a/modules/userId/userId.md b/modules/userId/userId.md index 1ec109ff309..9aea3e8d533 100644 --- a/modules/userId/userId.md +++ b/modules/userId/userId.md @@ -70,12 +70,6 @@ pbjs.setConfig({ params: { url: 'https://d9.flashtalking.com/d9core', // required, if not populated ftrack will not run } - }, { - name: 'parrableId', - params: { - // 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: 'identityLink', params: { @@ -372,16 +366,15 @@ pbjs.setConfig({ Example showing how to configure a `params` object to pass directly to bid adapters ``` - pbjs.setConfig({ -userSync: { -userIds: [{ -name: 'tncId', -params: { -providerId: "c8549079-f149-4529-a34b-3fa91ef257d1" -} -}], -syncDelay: 5000 -} + userSync: { + userIds: [{ + name: 'tncId', + params: { + url: 'https://js.tncid.app/remote.min.js' //Optional + } + }], + syncDelay: 5000 + } }); ``` diff --git a/modules/utiqSystem.js b/modules/utiqIdSystem.js similarity index 83% rename from modules/utiqSystem.js rename to modules/utiqIdSystem.js index 473dc5854a9..b5da87627a5 100644 --- a/modules/utiqSystem.js +++ b/modules/utiqIdSystem.js @@ -1,7 +1,7 @@ /** * This module adds Utiq provided by Utiq SA/NV to the User ID module * The {@link module:modules/userId} module is required - * @module modules/utiqSystem + * @module modules/utiqIdSystem * @requires module:modules/userId */ import { logInfo } from '../src/utils.js'; @@ -9,7 +9,7 @@ import { submodule } from '../src/hook.js'; import { getStorageManager } from '../src/storageManager.js'; import { MODULE_TYPE_UID } from '../src/activities/modules.js'; -const MODULE_NAME = 'utiq'; +const MODULE_NAME = 'utiqId'; const LOG_PREFIX = 'Utiq module'; export const storage = getStorageManager({ @@ -27,11 +27,17 @@ function getUtiqFromStorage() { let utiqPassStorage = JSON.parse( storage.getDataFromLocalStorage('utiqPass') ); - logInfo( - `${LOG_PREFIX}: Local storage utiqPass: ${JSON.stringify( - utiqPassStorage - )}` - ); + + const netIdAdtechpass = storage.getDataFromLocalStorage('netid_utiq_adtechpass'); + + if (netIdAdtechpass) { + logInfo( + `${LOG_PREFIX}: Local storage netid_utiq_adtechpass: ${netIdAdtechpass}` + ); + return { + utiq: netIdAdtechpass, + } + } if ( utiqPassStorage && @@ -40,12 +46,19 @@ function getUtiqFromStorage() { utiqPassStorage.connectId.idGraph.length > 0 ) { utiqPass = utiqPassStorage.connectId.idGraph[0]; + + logInfo( + `${LOG_PREFIX}: Local storage utiqPass: ${JSON.stringify( + utiqPassStorage + )}` + ); + + logInfo( + `${LOG_PREFIX}: Graph of utiqPass: ${JSON.stringify( + utiqPass + )}` + ); } - logInfo( - `${LOG_PREFIX}: Graph of utiqPass: ${JSON.stringify( - utiqPass - )}` - ); return { utiq: @@ -56,7 +69,7 @@ function getUtiqFromStorage() { } /** @type {Submodule} */ -export const utiqSubmodule = { +export const utiqIdSubmodule = { /** * Used to link submodule with config * @type {string} @@ -135,4 +148,4 @@ export const utiqSubmodule = { } }; -submodule('userId', utiqSubmodule); +submodule('userId', utiqIdSubmodule); diff --git a/modules/utiqSystem.md b/modules/utiqIdSystem.md similarity index 54% rename from modules/utiqSystem.md rename to modules/utiqIdSystem.md index d2c53480383..c7f4f95827f 100644 --- a/modules/utiqSystem.md +++ b/modules/utiqIdSystem.md @@ -5,7 +5,7 @@ Utiq ID Module. First, make sure to add the utiq submodule to your Prebid.js package with: ``` -gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqSystem +gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,utiqIdSystem ``` ## Parameter Descriptions @@ -15,8 +15,3 @@ gulp build --modules=userId,adfBidAdapter,ixBidAdapter,prebidServerBidAdapter,ut | name | String | The name of the module | `"utiq"` | | params | Object | Object with configuration parameters for utiq User Id submodule | - | | params.maxDelayTime | Integer | Max amount of time (in seconds) before looking into storage for data | 2500 | -| bidders | Array of Strings | An array of bidder codes to which this user ID may be sent. Currently required and supporting AdformOpenRTB | [`"adf"`, `"adformPBS"`, `"ix"`] | -| storage | Object | Local storage configuration object | - | -| storage.type | String | Type of the storage that would be used to store user ID. Must be `"html5"` to utilise HTML5 local storage. | `"html5"` | -| storage.name | String | The name of the key in local storage where the user ID will be stored. | `"utiq"` | -| storage.expires | Integer | How long (in days) the user ID information will be stored. For safety reasons, this information is required. | `1` | diff --git a/modules/validationFpdModule/index.js b/modules/validationFpdModule/index.js index 70af9d30ec3..2330c41099c 100644 --- a/modules/validationFpdModule/index.js +++ b/modules/validationFpdModule/index.js @@ -13,8 +13,8 @@ let optout; /** * Check if data passed is empty - * @param {*} value to test against - * @returns {Boolean} is value empty + * @param {*} data to test against + * @returns {Boolean} is data empty */ function isEmptyData(data) { let check = true; @@ -30,10 +30,10 @@ function isEmptyData(data) { /** * 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) + * @param {Object} obj data object + * @param {Array} required array of required keys + * @param {String} parent object path (for printing warning) + * @param {Number} i index of object value in the data array (for printing warning) * @returns {Boolean} is requirements fulfilled */ function getRequiredData(obj, required, parent, i) { @@ -51,8 +51,8 @@ function getRequiredData(obj, required, parent, i) { /** * Check if data type is valid - * @param {*} value to test against - * @param {Object} object containing type definition and if should be array bool + * @param {*} data value to test against + * @param {Object} mapping object containing type definition and if should be array bool * @returns {Boolean} is type fulfilled */ function typeValidation(data, mapping) { @@ -77,10 +77,10 @@ function typeValidation(data, mapping) { /** * 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 + * @param {Array} arr ortb2 data array + * @param {Object} child object defining child type and if array + * @param {String} path config path of data array + * @param {String} parent parent path for logging warnings * @returns {Array} validated/filtered data */ export function filterArrayData(arr, child, path, parent) { @@ -136,9 +136,9 @@ export function filterArrayData(arr, child, path, parent) { /** * 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 + * @param {Object} fpd ortb2 object + * @param {String} path config path of data array + * @param {String} parent parent path for logging warnings * @returns {Object} validated/filtered data */ export function validateFpd(fpd, path = '', parent = '') { @@ -190,6 +190,8 @@ export function validateFpd(fpd, path = '', parent = '') { /** * Run validation on global and bidder config data for ortb2 + * @param {Object} data global and bidder config data + * @returns {Object} validated data */ function runValidations(data) { return { @@ -200,6 +202,9 @@ function runValidations(data) { /** * Sets default values to ortb2 if exists and adds currency and ortb2 setConfig callbacks on init + * @param {Object} fpdConf configuration object + * @param {Object} data ortb2 data + * @returns {Object} processed data */ export function processFpd(fpdConf, data) { // Checks for existsnece of pubcid optout cookie/storage @@ -210,11 +215,11 @@ export function processFpd(fpdConf, data) { return (!fpdConf.skipValidations) ? runValidations(data) : data; } -/** @type {firstPartyDataSubmodule} */ +/** @type {{name: string, queue: number, processFpd: function}} */ export const validationSubmodule = { name: 'validation', queue: 1, processFpd } -submodule('firstPartyData', validationSubmodule) +submodule('firstPartyData', validationSubmodule); diff --git a/modules/vdoaiBidAdapter.js b/modules/vdoaiBidAdapter.js index ada843a6e45..8fda9cba593 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -1,6 +1,8 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import { deepClone, logError, deepAccess } from '../src/utils.js'; +import { config } from '../src/config.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -9,7 +11,86 @@ import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; */ const BIDDER_CODE = 'vdoai'; -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; +const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; + +function getFrameNesting() { + let topmostFrame = window; + let parent = window.parent; + try { + while (topmostFrame !== topmostFrame.parent) { + parent = topmostFrame.parent; + // eslint-disable-next-line no-unused-expressions + parent.location.href; + topmostFrame = topmostFrame.parent; + } + } catch (e) { } + return topmostFrame; +} + +/** + * Returns information about the page needed by the server in an object to be converted in JSON + * + * @returns {{location: *, referrer: (*|string), stack: (*|Array.), numIframes: (*|Number), wWidth: (*|Number), wHeight: (*|Number), sWidth, sHeight, date: string, timeOffset: number}} + */ +function getPageInfo(bidderRequest) { + const topmostFrame = getFrameNesting(); + return { + referrer: deepAccess(bidderRequest, 'refererInfo.ref', null), + stack: deepAccess(bidderRequest, 'refererInfo.stack', []), + numIframes: deepAccess(bidderRequest, 'refererInfo.numIframes', 0), + wWidth: topmostFrame.innerWidth, + location: deepAccess(bidderRequest, 'refererInfo.page', null), + wHeight: topmostFrame.innerHeight, + aWidth: topmostFrame.screen.availWidth, + aHeight: topmostFrame.screen.availHeight, + oWidth: topmostFrame.outerWidth, + oHeight: topmostFrame.outerHeight, + sWidth: topmostFrame.screen.width, + sHeight: topmostFrame.screen.height, + sLeft: 'screenLeft' in topmostFrame ? topmostFrame.screenLeft : topmostFrame.screenX, + sTop: 'screenTop' in topmostFrame ? topmostFrame.screenTop : topmostFrame.screenY, + xOffset: topmostFrame.pageXOffset, + docHeight: topmostFrame.document.body ? topmostFrame.document.body.scrollHeight : null, + hLength: history.length, + yOffset: topmostFrame.pageYOffset, + version: { + prebid_version: '$prebid.version$', + adapter_version: '1.0.0', + vendor: '$$PREBID_GLOBAL$$', + } + }; +} + +export function isSchainValid(schain) { + let isValid = false; + const requiredFields = ['asi', 'sid', 'hp']; + if (!schain || !schain.nodes) return isValid; + isValid = schain.nodes.reduce((status, node) => { + if (!status) return status; + return requiredFields.every(field => node.hasOwnProperty(field)); + }, true); + if (!isValid) { + logError('VDO.AI: required schain params missing'); + } + return isValid; +} + +function parseVideoSize(bid) { + const playerSize = bid.mediaTypes.video.playerSize; + if (typeof playerSize !== 'undefined' && Array.isArray(playerSize) && playerSize.length > 0) { + return getSizes(playerSize) + } + return []; +} + +function getSizes(sizes) { + const ret = []; + for (let i = 0; i < sizes.length; i++) { + const size = sizes[i]; + ret.push({ width: size[0], height: size[1] }) + } + return ret; +} export const spec = { code: BIDDER_CODE, @@ -21,7 +102,7 @@ export const spec = { * @return boolean True if this is a valid bid, and false otherwise. */ isBidRequestValid: function (bid) { - return !!(bid.params.placementId); + return !!(bid.params.placementId) && typeof bid.params.placementId === 'string'; }, /** @@ -31,24 +112,82 @@ export const spec = { * @param validBidRequests * @param bidderRequest */ + buildRequests: function (validBidRequests, bidderRequest) { if (validBidRequests.length === 0) { return []; } - return validBidRequests.map(bidRequest => { const sizes = getAdUnitSizes(bidRequest); - const payload = { + let payload = { placementId: bidRequest.params.placementId, sizes: sizes, bidId: bidRequest.bidId, - // TODO: is 'page' the right value here? - referer: bidderRequest.refererInfo.page, - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - id: bidRequest.auctionId, - mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' + mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner', + domain: bidderRequest.ortb2.site.domain, + publisherDomain: bidderRequest.ortb2.site.publisher.domain, + adUnitCode: bidRequest.adUnitCode, + bidder: bidRequest.bidder, + tmax: bidderRequest.timeout }; - bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); + + payload.bidderRequestId = bidRequest.bidderRequestId; + payload.auctionId = deepAccess(bidRequest, 'ortb2.source.tid'); + payload.transactionId = deepAccess(bidRequest, 'ortb2Imp.ext.tid'); + payload.gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + payload.ortb2Imp = deepAccess(bidRequest, 'ortb2Imp'); + + if (payload.mediaType === 'video') { + payload.context = bidRequest.mediaTypes.video.context; + payload.playerSize = parseVideoSize(bidRequest); + payload.mediaTypeInfo = deepClone(bidRequest.mediaTypes.video); + } + + if (typeof bidRequest.getFloor === 'function') { + let floor = bidRequest.getFloor({ + currency: 'USD', + mediaType: '*', + size: '*' + }); + if (floor && floor.floor && floor.currency === 'USD') { + payload.bidFloor = floor.floor; + } + } else if (bidRequest.params.bidFloor) { + payload.bidFloor = bidRequest.params.bidFloor; + } + + payload.pageInfo = getPageInfo(bidderRequest); + + if (bidderRequest && bidderRequest.gdprConsent) { + payload.gdprConsent = { + consentRequired: bidderRequest.gdprConsent.gdprApplies, + consentString: bidderRequest.gdprConsent.consentString, + addtlConsent: bidderRequest.gdprConsent.addtlConsent + }; + } + if (bidderRequest && bidderRequest.gppConsent) { + payload.gppConsent = { + applicableSections: bidderRequest.gppConsent.applicableSections, + consentString: bidderRequest.gppConsent.gppString, + } + } + if (bidderRequest && bidderRequest.ortb2) { + payload.ortb2 = bidderRequest.ortb2; + } + if (bidderRequest && bidderRequest.uspConsent) { + payload.usPrivacy = bidderRequest.uspConsent; + } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].schain && isSchainValid(validBidRequests[0].schain)) { + payload.schain = validBidRequests[0].schain; + } + if (validBidRequests && validBidRequests.length !== 0 && validBidRequests[0].userIdAsEids) { + payload.userId = validBidRequests[0].userIdAsEids; + } + let coppaOrtb2 = !!deepAccess(bidderRequest, 'ortb2.regs.coppa'); + let coppaConfig = config.getConfig('coppa'); + if (coppaOrtb2 === true || coppaConfig === true) { + payload.coppa = true; + } return { method: 'POST', url: ENDPOINT_URL, @@ -68,35 +207,24 @@ export const spec = { const bidResponses = []; const response = serverResponse.body; const creativeId = response.adid || 0; - // const width = response.w || 0; - const width = response.width; - // const height = response.h || 0; - const height = response.height; + const width = response.w; + const height = response.h; const cpm = response.price || 0; - response.rWidth = width; - response.rHeight = height; - const adCreative = response.vdoCreative; if (width !== 0 && height !== 0 && cpm !== 0 && creativeId !== 0) { - // const dealId = response.dealid || ''; const currency = response.cur || 'USD'; const netRevenue = true; - // const referrer = bidRequest.data.referer; const bidResponse = { requestId: response.bidId, cpm: cpm, width: width, height: height, creativeId: creativeId, - // dealId: dealId, currency: currency, netRevenue: netRevenue, ttl: 60, - // referrer: referrer, - // ad: response.adm - // ad: adCreative, mediaType: response.mediaType }; @@ -105,9 +233,9 @@ export const spec = { } else { bidResponse.ad = adCreative; } - if (response.adDomain) { + if (response.adomain) { bidResponse.meta = { - advertiserDomains: response.adDomain + advertiserDomains: response.adomain }; } bidResponses.push(bidResponse); @@ -131,7 +259,7 @@ export const spec = { return []; }, - onTImeout: function(data) {}, + onTimeout: function(data) {}, onBidWon: function(bid) {}, onSetTargeting: function(bid) {} }; diff --git a/modules/viantOrtbBidAdapter.js b/modules/viantOrtbBidAdapter.js index d056dfeb2eb..cfc4c450db8 100644 --- a/modules/viantOrtbBidAdapter.js +++ b/modules/viantOrtbBidAdapter.js @@ -5,7 +5,8 @@ import {ortbConverter} from '../libraries/ortbConverter/converter.js' import {deepAccess, getBidIdParameter, logError} from '../src/utils.js'; const BIDDER_CODE = 'viant'; -const ENDPOINT = 'https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder' +const ENDPOINT = 'https://bidders-us.adelphic.net/d/rtb/v25/prebid/bidder' +const ADAPTER_VERSION = '2.0.0'; const DEFAULT_BID_TTL = 300; const DEFAULT_CURRENCY = 'USD'; @@ -85,6 +86,25 @@ function createRequest(bidRequests, bidderRequest, mediaType) { if (!data.regs.ext) data.regs.ext = {}; data.regs.ext.us_privacy = bidderRequest.uspConsent; } + let imp = data.imp || []; + let dealsMap = new Map(); + if (bidderRequest.bids) { + bidderRequest.bids.forEach(bid => { + if (bid.ortb2Imp && bid.ortb2Imp.pmp) { + dealsMap.set(bid.bidId, bid.ortb2Imp.pmp); + } + }); + } + imp.forEach((element) => { + let deals = dealsMap.get(element.id); + if (deals) { + element.pmp = deals; + } + }); + data.ext = data.ext || {}; + data.ext.viant = { + adapterVersion: ADAPTER_VERSION + }; return { method: 'POST', url: ENDPOINT, diff --git a/modules/vidazooBidAdapter.js b/modules/vidazooBidAdapter.js index c5e35c6b138..ed732a4814a 100644 --- a/modules/vidazooBidAdapter.js +++ b/modules/vidazooBidAdapter.js @@ -1,494 +1,46 @@ -import { - _each, - deepAccess, - isFn, - parseSizesInput, - parseUrl, - uniques, - isArray, - formatQS, - triggerPixel -} 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 {bidderSettings} from '../src/bidderSettings.js'; -import {config} from '../src/config.js'; -import {chunk} from '../libraries/chunk/chunk.js'; +import { + createSessionId, + isBidRequestValid, + getCacheOpt, + getNextDealId, + onBidWon, + createUserSyncGetter, + getVidazooSessionId, + createBuildRequestsFn, + createInterpretResponseFn +} from '../libraries/vidazooUtils/bidderUtils.js'; +import {OPT_CACHE_KEY, OPT_TIME_KEY} from '../libraries/vidazooUtils/constants.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 DEAL_ID_EXPIRY = 1000 * 60 * 15; -const UNIQUE_DEAL_ID_EXPIRY = 1000 * 60 * 60; -const SESSION_ID_KEY = 'vidSid'; -const OPT_CACHE_KEY = 'vdzwopt'; -export const webSessionId = 'wsid_' + parseInt(Date.now() * Math.random()); -const storage = getStorageManager({bidderCode: BIDDER_CODE}); - -function getTopWindowQueryParams() { - try { - const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} +export const storage = getStorageManager({bidderCode: BIDDER_CODE}); +export const webSessionId = createSessionId(); 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 !!(extractCID(params) && extractPID(params)); -} - -function buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const { - params, - bidId, - userId, - adUnitCode, - schain, - mediaTypes, - ortb2Imp, - bidderRequestId, - bidRequestsCount, - bidderRequestsCount, - bidderWinsCount - } = bid; - const {ext} = params; - let {bidFloor} = params; - const hashUrl = hashCode(topWindowUrl); - const dealId = getNextDealId(hashUrl); - const uniqueDealId = getUniqueDealId(hashUrl); - const sId = getVidazooSessionId(); - const pId = extractPID(params); - const ptrace = getCacheOpt(); - const isStorageAllowed = bidderSettings.get(BIDDER_CODE, 'storageAllowed'); - - const gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot', ''); - const cat = deepAccess(bidderRequest, 'ortb2.site.cat', []); - const pagecat = deepAccess(bidderRequest, 'ortb2.site.pagecat', []); - const contentData = deepAccess(bidderRequest, 'ortb2.site.content.data', []); - const userData = deepAccess(bidderRequest, 'ortb2.user.data', []); - - if (isFn(bid.getFloor)) { - const floorInfo = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*' - }); - - if (floorInfo.currency === 'USD') { - bidFloor = floorInfo.floor; - } - } - - let data = { - url: encodeURIComponent(topWindowUrl), - uqs: getTopWindowQueryParams(), - cb: Date.now(), - bidFloor: bidFloor, - bidId: bidId, - referrer: bidderRequest.refererInfo.ref, - adUnitCode: adUnitCode, - publisherId: pId, - sessionId: sId, - sizes: sizes, - dealId: dealId, - uniqueDealId: uniqueDealId, - bidderVersion: BIDDER_VERSION, - prebidVersion: '$prebid.version$', - res: `${screen.width}x${screen.height}`, - schain: schain, - mediaTypes: mediaTypes, - ptrace: ptrace, - isStorageAllowed: isStorageAllowed, - gpid: gpid, - cat: cat, - contentData, - userData: userData, - pagecat: pagecat, - transactionId: ortb2Imp?.ext?.tid, - bidderRequestId: bidderRequestId, - bidRequestsCount: bidRequestsCount, - bidderRequestsCount: bidderRequestsCount, - bidderWinsCount: bidderWinsCount, - bidderTimeout: bidderTimeout, - webSessionId: webSessionId - }; - - appendUserIdsToRequestPayload(data, userId); - - const sua = deepAccess(bidderRequest, 'ortb2.device.sua'); - - if (sua) { - data.sua = sua; - } - - if (bidderRequest.gdprConsent) { - if (bidderRequest.gdprConsent.consentString) { - data.gdprConsent = bidderRequest.gdprConsent.consentString; - } - if (bidderRequest.gdprConsent.gdprApplies !== undefined) { - data.gdpr = bidderRequest.gdprConsent.gdprApplies ? 1 : 0; - } - } - if (bidderRequest.uspConsent) { - data.usPrivacy = bidderRequest.uspConsent; - } - - if (bidderRequest.gppConsent) { - data.gppString = bidderRequest.gppConsent.gppString; - data.gppSid = bidderRequest.gppConsent.applicableSections; - } else if (bidderRequest.ortb2?.regs?.gpp) { - data.gppString = bidderRequest.ortb2.regs.gpp; - data.gppSid = bidderRequest.ortb2.regs.gpp_sid; - } - - if (bidderRequest.fledgeEnabled) { - const fledge = deepAccess(bidderRequest, 'ortb2Imp.ext.ae'); - if (fledge) { - data.fledge = fledge; - } - } - - _each(ext, (value, key) => { - data['ext.' + key] = value; - }); - - return data; -} - -function buildRequest(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) { - const {params} = bid; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout); - const dto = { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: data - }; - return dto; -} - -function buildSingleRequest(bidRequests, bidderRequest, topWindowUrl, bidderTimeout) { - const {params} = bidRequests[0]; - const cId = extractCID(params); - const subDomain = extractSubDomain(params); - const data = bidRequests.map(bid => { - const sizes = parseSizesInput(bid.sizes); - return buildRequestData(bid, topWindowUrl, sizes, bidderRequest, bidderTimeout) - }); - const chunkSize = Math.min(20, config.getConfig('vidazoo.chunkSize') || 10); - - const chunkedData = chunk(data, chunkSize); - return chunkedData.map(chunk => { - return { - method: 'POST', - url: `${createDomain(subDomain)}/prebid/multi/${cId}`, - data: { - bids: chunk - } - }; - }); -} - -function appendUserIdsToRequestPayload(payloadRef, userIds) { - let key; - _each(userIds, (userId, idSystemProviderName) => { - key = `uid.${idSystemProviderName}`; - switch (idSystemProviderName) { - case 'digitrustid': - payloadRef[key] = deepAccess(userId, 'data.id'); - break; - 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; - } - }); -} - -function buildRequests(validBidRequests, bidderRequest) { - // TODO: does the fallback make sense here? - const topWindowUrl = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - const bidderTimeout = config.getConfig('bidderTimeout'); - - const singleRequestMode = config.getConfig('vidazoo.singleRequest'); - - const requests = []; - - if (singleRequestMode) { - // banner bids are sent as a single request - const bannerBidRequests = validBidRequests.filter(bid => isArray(bid.mediaTypes) ? bid.mediaTypes.includes(BANNER) : bid.mediaTypes[BANNER] !== undefined); - if (bannerBidRequests.length > 0) { - const singleRequests = buildSingleRequest(bannerBidRequests, bidderRequest, topWindowUrl, bidderTimeout); - requests.push(...singleRequests); - } - - // video bids are sent as a single request for each bid - - const videoBidRequests = validBidRequests.filter(bid => bid.mediaTypes[VIDEO] !== undefined); - videoBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } else { - validBidRequests.forEach(validBidRequest => { - const sizes = parseSizesInput(validBidRequest.sizes); - const request = buildRequest(validBidRequest, topWindowUrl, sizes, bidderRequest, bidderTimeout); - requests.push(request); - }); - } - return requests; -} - -function interpretResponse(serverResponse, request) { - if (!serverResponse || !serverResponse.body) { - return []; - } - - const singleRequestMode = config.getConfig('vidazoo.singleRequest'); - const reqBidId = deepAccess(request, 'data.bidId'); - const {results} = serverResponse.body; - - let output = []; - - try { - results.forEach((result, i) => { - const { - creativeId, - ad, - price, - exp, - width, - height, - currency, - bidId, - nurl, - advertiserDomains, - metaData, - mediaType = BANNER - } = result; - if (!ad || !price) { - return; - } - - const response = { - requestId: (singleRequestMode && bidId) ? bidId : reqBidId, - cpm: price, - width: width, - height: height, - creativeId: creativeId, - currency: currency || CURRENCY, - netRevenue: true, - ttl: exp || TTL_SECONDS, - }; - - if (nurl) { - response.nurl = nurl; - } - - if (metaData) { - Object.assign(response, { - meta: metaData - }) - } else { - Object.assign(response, { - meta: { - advertiserDomains: advertiserDomains || [] - } - }) - } - - if (mediaType === BANNER) { - Object.assign(response, { - ad: ad, - }); - } else { - Object.assign(response, { - vastXml: ad, - mediaType: VIDEO - }); - } - output.push(response); - }); - - return output; - } catch (e) { - return []; - } -} - -function getUserSyncs(syncOptions, responses, gdprConsent = {}, uspConsent = '', gppConsent = {}) { - let syncs = []; - const {iframeEnabled, pixelEnabled} = syncOptions; - const {gdprApplies, consentString = ''} = gdprConsent; - const {gppString, applicableSections} = gppConsent; - - const cidArr = responses.filter(resp => deepAccess(resp, 'body.cid')).map(resp => resp.body.cid).filter(uniques); - let params = `?cid=${encodeURIComponent(cidArr.join(','))}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${encodeURIComponent(consentString || '')}&us_privacy=${encodeURIComponent(uspConsent || '')}`; +function createUniqueRequestData(hashUrl) { + const dealId = getNextDealId(storage, hashUrl); + const sessionId = getVidazooSessionId(storage); + const ptrace = getCacheOpt(storage, OPT_CACHE_KEY); + const vdzhum = getCacheOpt(storage, OPT_TIME_KEY); - if (gppString && applicableSections?.length) { - params += '&gpp=' + encodeURIComponent(gppString); - params += '&gpp_sid=' + encodeURIComponent(applicableSections.join(',')); - } - - if (iframeEnabled) { - syncs.push({ - type: 'iframe', - url: `https://sync.cootlogix.com/api/sync/iframe/${params}` - }); - } - if (pixelEnabled) { - syncs.push({ - type: 'image', - url: `https://sync.cootlogix.com/api/sync/image/${params}` - }); - } - return syncs; -} - -/** - * @param {Bid} bid - */ -function onBidWon(bid) { - if (!bid.nurl) { - return; - } - const wonBid = { - adId: bid.adId, - creativeId: bid.creativeId, - auctionId: bid.auctionId, - transactionId: bid.transactionId, - adUnitCode: bid.adUnitCode, - cpm: bid.cpm, - currency: bid.currency, - originalCpm: bid.originalCpm, - originalCurrency: bid.originalCurrency, - netRevenue: bid.netRevenue, - mediaType: bid.mediaType, - timeToRespond: bid.timeToRespond, - status: bid.status, + return { + dealId: dealId, sessionId: sessionId, ptrace: ptrace, vdzhum: vdzhum, webSessionId: webSessionId }; - const qs = formatQS(wonBid); - const url = bid.nurl + (bid.nurl.indexOf('?') === -1 ? '?' : '&') + qs; - triggerPixel(url); -} - -export function hashCode(s, prefix = '_') { - const l = s.length; - let h = 0 - let i = 0; - if (l > 0) { - while (i < l) { - h = (h << 5) - h + s.charCodeAt(i++) | 0; - } - } - return prefix + h; } -export function getNextDealId(key, expiry = DEAL_ID_EXPIRY) { - try { - 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, timestamp); - return nextValue; - } catch (e) { - return 0; - } -} - -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) || ''; -} - -export function getCacheOpt() { - let data = storage.getDataFromLocalStorage(OPT_CACHE_KEY); - if (!data) { - data = String(Date.now()); - storage.setDataInLocalStorage(OPT_CACHE_KEY, data); - } - - return data; -} - -export function getStorageItem(key) { - try { - return tryParseJSON(storage.getDataFromLocalStorage(key)); - } catch (e) { - } - - return null; -} - -export function setStorageItem(key, value, timestamp) { - try { - 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; - } -} +const buildRequests = createBuildRequestsFn(createDomain, createUniqueRequestData, storage, BIDDER_CODE, BIDDER_VERSION, true); +const interpretResponse = createInterpretResponseFn(BIDDER_CODE, true); +const getUserSyncs = createUserSyncGetter({ + iframeSyncUrl: 'https://sync.cootlogix.com/api/sync/iframe', imageSyncUrl: 'https://sync.cootlogix.com/api/sync/image' +}); export const spec = { code: BIDDER_CODE, diff --git a/modules/videoModule/coreVideo.js b/modules/videoModule/coreVideo.js index fc54d0d0b98..4ac5f090334 100644 --- a/modules/videoModule/coreVideo.js +++ b/modules/videoModule/coreVideo.js @@ -104,7 +104,6 @@ import { ParentModule, SubmoduleBuilder } from '../../libraries/video/shared/par /** * @summary Maps a Video Provider factory to the video player's vendor code. - * @type {vendorSubmoduleDirectory} */ const videoVendorDirectory = {}; diff --git a/modules/videoModule/gamAdServerSubmodule.js b/modules/videoModule/gamAdServerSubmodule.js index 87db71ae38b..728ee4d060e 100644 --- a/modules/videoModule/gamAdServerSubmodule.js +++ b/modules/videoModule/gamAdServerSubmodule.js @@ -4,7 +4,6 @@ import { getGlobal } from '../../src/prebidGlobal.js'; /** * @constructor * @param {Object} dfpModule_ - the DFP ad server module - * @returns {AdServerProvider} */ function GamAdServerProvider(dfpModule_) { const dfp = dfpModule_; diff --git a/modules/videobyteBidAdapter.js b/modules/videobyteBidAdapter.js index 8cedf9ac16a..c34d3ecb097 100644 --- a/modules/videobyteBidAdapter.js +++ b/modules/videobyteBidAdapter.js @@ -19,6 +19,7 @@ const VIDEO_ORTB_PARAMS = [ 'minduration', 'maxduration', 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -191,16 +192,6 @@ function buildRequestData(bidRequest, bidderRequest) { } }); - // 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', @@ -223,8 +214,8 @@ function buildRequestData(bidRequest, bidderRequest) { id: '1', video: video, secure: isSecure() ? 1 : 0, - bidfloor: floorData.floor, - bidfloorcur: floorData.currency + bidfloor: floorData?.floor, + bidfloorcur: floorData?.currency } ], site: { diff --git a/modules/videoheroesBidAdapter.js b/modules/videoheroesBidAdapter.js index ee2c2deef8b..4adabacc33b 100644 --- a/modules/videoheroesBidAdapter.js +++ b/modules/videoheroesBidAdapter.js @@ -1,8 +1,8 @@ -import { isEmpty, parseUrl, isStr, triggerPixel } from '../src/utils.js'; +import { triggerPixel, isStr } from '../src/utils.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; +import { parseNative } from '../libraries/braveUtils/index.js'; +import { buildRequests, interpretResponse } from '../libraries/braveUtils/buildAndInterpret.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -13,158 +13,15 @@ const BIDDER_CODE = 'videoheroes'; const DEFAULT_CUR = 'USD'; const ENDPOINT_URL = `https://point.contextualadv.com/?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) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - 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 = { - // TODO: fix transactionId leak: https://github.com/prebid/Prebid.js/issues/9781 - // Also, `id` is not in the ORTB native spec - id: br.transactionId, - ver: '1.2', - request: createNativeRequest(br) - }; - } - return impObject; - }); - - let page = bidderRequest.refererInfo.page || bidderRequest.refererInfo.topmostLocation; - - let data = { - id: bidderRequest.bidderRequestId, - cur: [ DEFAULT_CUR ], - device: { - w: screen.width, - h: screen.height, - language: (navigator && navigator.language) ? navigator.language.indexOf('-') != -1 ? navigator.language.split('-')[0] : navigator.language : '', - ua: navigator.userAgent, - }, - site: { - domain: parseUrl(page).hostname, - page: page, - }, - tmax: bidderRequest.timeout, - imp - }; - - if (bidderRequest.refererInfo.ref) { - data.site.ref = bidderRequest.refererInfo.ref; - } - - if (bidderRequest.gdprConsent) { - data['regs'] = {'ext': {'gdpr': bidderRequest.gdprConsent.gdprApplies ? 1 : 0}}; - data['user'] = {'ext': {'consent': bidderRequest.gdprConsent.consentString ? bidderRequest.gdprConsent.consentString : ''}}; - } - - if (bidderRequest.uspConsent !== undefined) { - if (!data['regs'])data['regs'] = {'ext': {}}; - data['regs']['ext']['us_privacy'] = bidderRequest.uspConsent; - } - - if (config.getConfig('coppa') === true) { - if (!data['regs'])data['regs'] = {'coppa': 1}; - else data['regs']['coppa'] = 1; - } - - if (validBidRequests[0].schain) { - data['source'] = {'ext': {'schain': validBidRequests[0].schain}}; - } - - return { - method: 'POST', - url: endpointURL, - data: data - }; - }, - - /** - * Unpack the response from the server into a list of bids. - * - * @param {*} serverResponse A successful response from the server. - * @return {Bid[]} An array of bids which were nested inside the server. - */ - interpretResponse: (serverResponse) => { - if (!serverResponse || isEmpty(serverResponse.body)) return []; - - let bids = []; - serverResponse.body.seatbid.forEach(response => { - response.bid.forEach(bid => { - let mediaType = bid.ext && bid.ext.mediaType ? bid.ext.mediaType : 'banner'; - - let bidObj = { - requestId: bid.impid, - cpm: bid.price, - width: bid.w, - height: bid.h, - ttl: 1200, - currency: DEFAULT_CUR, - netRevenue: true, - creativeId: bid.crid, - dealId: bid.dealid || null, - mediaType: mediaType - }; - - switch (mediaType) { - case 'video': - bidObj.vastUrl = bid.adm; - break; - case 'native': - bidObj.native = parseNative(bid.adm); - break; - default: - bidObj.ad = bid.adm; - } + isBidRequestValid: (bid) => !!(bid.params.placementId && bid.params.placementId.toString().length === 32), - bids.push(bidObj); - }); - }); + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT_URL, DEFAULT_CUR), - return bids; - }, + interpretResponse: (serverResponse) => interpretResponse(serverResponse, DEFAULT_CUR, parseNative), onBidWon: (bid) => { if (isStr(bid.nurl) && bid.nurl !== '') { @@ -173,89 +30,4 @@ export const spec = { } }; -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/videojsVideoProvider.js b/modules/videojsVideoProvider.js index 7764e8af995..0be4c6feede 100644 --- a/modules/videojsVideoProvider.js +++ b/modules/videojsVideoProvider.js @@ -6,13 +6,16 @@ import { } from '../libraries/video/constants/events.js'; // missing events: , AD_BREAK_START, , AD_BREAK_END, VIEWABLE, BUFFER, CAST, PLAYLIST_COMPLETE, RENDITION_UPDATE, PLAY_ATTEMPT_FAILED, AUTOSTART_BLOCKED import { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION, PLAYBACK_END } from '../libraries/video/constants/ortb.js'; import { VIDEO_JS_VENDOR } from '../libraries/video/constants/vendorCodes.js'; import { submodule } from '../src/hook.js'; import stateFactory from '../libraries/video/shared/state.js'; import { PLAYBACK_MODE } from '../libraries/video/constants/constants.js'; import { getEventHandler } from '../libraries/video/shared/eventHandler.js'; +/** + * @typedef {import('../libraries/video/shared/state.js').State} State + */ /* Plugins of interest: @@ -146,8 +149,9 @@ export function VideojsProvider(providerConfig, vjs_, adState_, timeState_, call // ~ Sort of resolved check if the player has a source to tell if the placement is instream // Still cannot reliably check what type of placement the player is if its outstream // i.e. we can't tell if its interstitial, in article, etc. + // update: cannot infer instream ever, always need declarations if (player.src()) { - video.placement = PLACEMENT.INSTREAM; + video.plcmt = PLCMT.ACCOMPANYING_CONTENT; } // Placement according to IQG Guidelines 4.2.8 diff --git a/modules/vidoomyBidAdapter.js b/modules/vidoomyBidAdapter.js index c9ac9fae0f4..9f341b42ff5 100644 --- a/modules/vidoomyBidAdapter.js +++ b/modules/vidoomyBidAdapter.js @@ -1,4 +1,4 @@ -import {deepAccess, logError, parseSizesInput} from '../src/utils.js'; +import {deepAccess, isPlainObject, logError, parseSizesInput} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -88,7 +88,7 @@ function getBidFloor(bid, mediaType, sizes, bidfloor) { var size = sizes && sizes.length > 0 ? 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))) { + if (isPlainObject(floorInfo) && floorInfo.currency === 'USD' && !isNaN(parseFloat(floorInfo.floor))) { floor = Math.max(bidfloor, parseFloat(floorInfo.floor)); } } diff --git a/modules/viouslyBidAdapter.js b/modules/viouslyBidAdapter.js index 5ccca7590dd..e474a8de93c 100644 --- a/modules/viouslyBidAdapter.js +++ b/modules/viouslyBidAdapter.js @@ -2,7 +2,7 @@ import { deepAccess, logError, parseUrl, parseSizesInput, triggerPixel } from '. 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'; // eslint-disable-line prebid/validate-imports +import {find} from '../src/polyfill.js'; const BIDDER_CODE = 'viously'; const GVLID = 1028; diff --git a/modules/viqeoBidAdapter.js b/modules/viqeoBidAdapter.js index 28f4de1fd52..aac9ee69a24 100644 --- a/modules/viqeoBidAdapter.js +++ b/modules/viqeoBidAdapter.js @@ -1,7 +1,15 @@ -import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {logError, logInfo, _each, mergeDeep, isFn, isNumber, isPlainObject} from '../src/utils.js' -import {VIDEO} from '../src/mediaTypes.js'; -import {Renderer} from '../src/Renderer.js'; +import { registerBidder } from '../src/adapters/bidderFactory.js'; +import { + logError, + logInfo, + _each, + mergeDeep, + isFn, + isNumber, + isPlainObject, +} from '../src/utils.js'; +import { VIDEO } from '../src/mediaTypes.js'; +import { Renderer } from '../src/Renderer.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -12,36 +20,48 @@ import {Renderer} from '../src/Renderer.js'; */ const BIDDER_CODE = 'viqeo'; -const DEFAULT_MIMES = ['application/javascript']; -const VIQEO_ENDPOINT = 'https://ads.betweendigital.com/openrtb_bid'; +const DEFAULT_MIMES = [ + 'video/3gpp', + 'video/mp4', + 'video/mpeg', + 'video/webm', + 'application/javascript', +]; +const VIQEO_ENDPOINT = 'https://ad.vqserve.com/ads/prebid'; const RENDERER_URL = 'https://cdn.viqeo.tv/js/vq_starter.js'; const DEFAULT_CURRENCY = 'USD'; -const DEFAULT_SSPID = 44697; function getBidFloor(bid) { - const {floor, currency} = bid.params; + const { floor, currency } = bid.params; const curr = currency || DEFAULT_CURRENCY; if (!isFn(bid.getFloor)) { - return {floor: isNumber(floor) ? floor : 0, currency: curr}; + return { floor: isNumber(floor) ? floor : 0, currency: curr }; } - const floorInfo = bid.getFloor({currency: curr, mediaType: VIDEO, size: '*'}); - if (isPlainObject(floorInfo) && isNumber(floorInfo.floor) && floorInfo.currency === curr) { + const floorInfo = bid.getFloor({ + currency: curr, + mediaType: VIDEO, + size: '*', + }); + if ( + isPlainObject(floorInfo) && + isNumber(floorInfo.floor) && + floorInfo.currency === curr + ) { return floorInfo; } - return {floor: floor || 0, currency: currency || DEFAULT_CURRENCY}; + return { floor: floor || 0, currency: currency || DEFAULT_CURRENCY }; } -function getVideoTargetingParams({mediaTypes: {video}}) { +function getVideoTargetingParams({ mediaTypes: { video } }) { const result = {}; - Object.keys(Object(video)) - .forEach(key => { - if (key === 'playerSize') { - result.w = video.playerSize[0][0]; - result.h = video.playerSize[0][1]; - } else if (key !== 'context') { - result[key] = video[key]; - } - }) + Object.keys(Object(video)).forEach((key) => { + if (key === 'playerSize') { + result.w = video.playerSize[0][0]; + result.h = video.playerSize[0][1]; + } else if (key !== 'context') { + result[key] = video[key]; + } + }); return result; } @@ -55,20 +75,20 @@ export const spec = { * @param {BidRequest} bidRequest The bid params to validate. * @return boolean True if this is a valid bid, and false otherwise. */ - isBidRequestValid: ({params}) => { + isBidRequestValid: ({ params }) => { if (!params) { logError('failed validation: params not declared'); return false; } - if (!params.user && !params.user?.buyeruid) { - logError('failed validation: user.buyeruid not declared'); + if (!params.tagId) { + logError('failed validation: tagId not declared'); return false; } if (!params.playerOptions) { logError('failed validation: playerOptions not declared'); return false; } - const {profileId, videoId, playerId} = params.playerOptions; + const { profileId, videoId, playerId } = params.playerOptions; if (!profileId) { logError('failed validation: profileId not declared'); return false; @@ -88,46 +108,42 @@ export const spec = { const bidRequests = []; _each(validBidRequests, (bid, i) => { const { - params: {test, sspId, endpointUrl}, - mediaTypes: {video}, + params: { test, tagId, endpointUrl, bcat, badv }, + mediaTypes: { video }, } = bid; const ortb2 = bid.ortb2 || {}; const user = bid.params.user || {}; const device = bid.params.device || {}; const site = bid.params.site || {}; - const w = window; const floorInfo = getBidFloor(bid); + const data = { id: bid.bidId, test, - imp: [{ - id: `${i}`, - tagid: bid.adUnitCode, - video: { - ...getVideoTargetingParams(bid), - mimes: video.mimes || DEFAULT_MIMES, + imp: [ + { + id: `${i}`, + video: { + ...getVideoTargetingParams(bid), + mimes: video.mimes || DEFAULT_MIMES, + }, + tagid: `${tagId}`, + bidfloor: floorInfo.floor, + bidfloorcur: floorInfo.currency, + secure: 1, }, - bidfloor: floorInfo.floor, - bidfloorcur: floorInfo.currency, - secure: 1 - }], - site: test === 1 ? { - page: 'https://viqeo.tv', - domain: 'viqeo.tv' - } : mergeDeep({ - domain: w.location.hostname, - page: w.location.href - }, ortb2.site, site), - device: mergeDeep({ - w: w.screen.width, - h: w.screen.height, - ua: w.navigator.userAgent, - }, ortb2.device, device), - user: mergeDeep({...user}, ortb2.user), - app: bid.params.app, + ], + site: mergeDeep( + site, + ortb2.site + ), + device: mergeDeep(device, ortb2.device), + user: mergeDeep(user, ortb2.user), + bcat: ortb2.bcat || bcat, + badv: ortb2.badv || badv }; bidRequests.push({ - url: endpointUrl || `${VIQEO_ENDPOINT}/?sspId=${sspId || DEFAULT_SSPID}`, + url: endpointUrl || `${VIQEO_ENDPOINT}`, method: 'POST', data, bids: validBidRequests, @@ -148,24 +164,24 @@ export const spec = { return []; } try { - const {id, seatbid, cur} = serverResponse.body; + const { id, seatbid, cur } = serverResponse.body; _each(seatbid, (sb) => { - const {bid} = sb; + const { bid } = sb; _each(bid, (b) => { - const bidRequest = bidRequests.bids.find(({bidId}) => bidId === id); + const bidRequest = bidRequests.bids.find(({ bidId }) => bidId === id); const renderer = Renderer.install({ url: bidRequest?.params?.renderUrl || RENDERER_URL, }); - renderer.setRender((bid) => { + renderer.setRender((bid, doc) => { if (window.VIQEO) { - window.VIQEO.renderPrebid(bid); + window.VIQEO.renderPrebid(bid, doc); } else { logError('failed get window.VIQEO'); } }); bidResponses.push({ requestId: id, - currency: cur, + currency: cur || DEFAULT_CURRENCY, cpm: b.price, ttl: b.exp, netRevenue: true, @@ -176,13 +192,18 @@ export const spec = { vastUrl: b.nurl, mediaType: VIDEO, renderer, - }) - }) + meta: { + secondaryCatIds: b.cat, + attr: b.attr, + advertiserDomains: b.adomain, + }, + }); + }); }); } catch (error) { logError(error); } return bidResponses; }, -} +}; registerBidder(spec); diff --git a/modules/viqeoBidAdapter.md b/modules/viqeoBidAdapter.md index b4a020bf057..b3a0c59d3a5 100644 --- a/modules/viqeoBidAdapter.md +++ b/modules/viqeoBidAdapter.md @@ -11,23 +11,21 @@ Viqeo Bidder Adapter for Prebid.js. About: https://viqeo.tv/ ### Bid params {: .table .table-bordered .table-striped } -| Name | Scope | Description | Example | Type | -|-----------------------------|----------|----------------------------------------------------------------------------------------------------------------------------|--------------------------|-----------| -| `user` | required | The object containing user data (See OpenRTB spec) | `user: {}` | `object` | -| `user.buyeruid` | required | User id | `"12345"` | `string` | -| `playerOptions` | required | The object containing Viqeo player options | `playerOptions: {}` | `object` | -| `playerOptions.profileId` | required | Viqeo profile id | `1382` | `number` | -| `playerOptions.videId` | optional | Viqeo video id | `"ed584da454c7205ca7e4"` | `string` | -| `playerOptions.playerId` | optional | Viqeo player id | `1` | `number` | -| `device` | optional | The object containing device data (See OpenRTB spec) | `device: {}` | `object` | -| `site` | optional | The object containing site data (See OpenRTB spec) | `site: {}` | `object` | -| `app` | optional | The object containing app data (See OpenRTB spec) | `app: {}` | `object` | -| `floor` | optional | Bid floor price | `0.5` | `number` | -| `currency` | optional | 3-letter ISO 4217 code defining the currency of the bid. | `EUR` | `string` | -| `test` | optional | Flag which will induce a sample bid response when true; only set to true for testing purposes (1 = true, 0 = false) | `1` | `integer` | -| `sspId` | optional | For debug, request id | `1` | `number` | -| `renderUrl` | optional | For debug, script player url | `"https://viqeo.tv"` | `string` | -| `endpointUrl` | optional | For debug, api endpoint | `"https://viqeo.tv"` | `string` | +| Name | Scope | Description | Example | Type | +|-----------------------------|----------|---------------------------------------------------------------------------------------------------------------------|--------------------------|-----------| +| `tagid` | required | The unique identifier of the ad placement. Could be obtained from the Viqeo UI or from your account manager. | `2` | `string` | +| `playerOptions` | required | The object containing Viqeo player options | `playerOptions: {}` | `object` | +| `playerOptions.profileId` | required | Viqeo profile id | `1382` | `number` | +| `playerOptions.videId` | optional | Viqeo video id | `"ed584da454c7205ca7e4"` | `string` | +| `playerOptions.playerId` | optional | Viqeo player id | `1` | `number` | +| `user` | optional | The object containing user data (See OpenRTB spec) | `user: {}` | `object` | +| `device` | optional | The object containing device data (See OpenRTB spec) | `device: {}` | `object` | +| `site` | optional | The object containing site data (See OpenRTB spec) | `site: {}` | `object` | +| `floor` | optional | Bid floor price | `0.5` | `number` | +| `currency` | optional | 3-letter ISO 4217 code defining the currency of the bid. | `EUR` | `string` | +| `test` | optional | Flag which will induce a sample bid response when true; only set to true for testing purposes (1 = true, 0 = false) | `1` | `integer` | +| `renderUrl` | optional | For debug, script player url | `"https://viqeo.tv"` | `string` | +| `endpointUrl` | optional | For debug, api endpoint | `"https://viqeo.tv"` | `string` | # Test Parameters ``` @@ -42,9 +40,7 @@ Viqeo Bidder Adapter for Prebid.js. About: https://viqeo.tv/ bids: [{ bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', playerOptions: { videoId: 'ed584da454c7205ca7e4', profileId: 1382, diff --git a/modules/visiblemeasuresBidAdapter.js b/modules/visiblemeasuresBidAdapter.js index e77477c812b..1c51944c538 100644 --- a/modules/visiblemeasuresBidAdapter.js +++ b/modules/visiblemeasuresBidAdapter.js @@ -1,212 +1,19 @@ -import { isFn, deepAccess, logMessage, logError } from '../src/utils.js'; -import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; - import { registerBidder } from '../src/adapters/bidderFactory.js'; import { BANNER, NATIVE, VIDEO } from '../src/mediaTypes.js'; -import { config } from '../src/config.js'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../libraries/teqblazeUtils/bidderUtils.js'; const BIDDER_CODE = 'visiblemeasures'; const AD_URL = 'https://us-e.visiblemeasures.com/pbjs'; const SYNC_URL = 'https://cs.visiblemeasures.com'; -function isBidResponseValid(bid) { - if (!bid.requestId || !bid.cpm || !bid.creativeId || !bid.ttl || !bid.currency) { - return false; - } - - switch (bid.mediaType) { - case BANNER: - return Boolean(bid.width && bid.height && bid.ad); - case VIDEO: - return Boolean(bid.vastUrl || bid.vastXml); - case NATIVE: - return Boolean(bid.native && bid.native.impressionTrackers && bid.native.impressionTrackers.length); - default: - return false; - } -} - -function getPlacementReqData(bid) { - const { params, bidId, mediaTypes } = bid; - const schain = bid.schain || {}; - const { placementId, endpointId } = params; - const bidfloor = getBidFloor(bid); - - const placement = { - bidId, - schain, - bidfloor - }; - - if (placementId) { - placement.placementId = placementId; - placement.type = 'publisher'; - } else if (endpointId) { - placement.endpointId = endpointId; - placement.type = 'network'; - } - - if (mediaTypes && mediaTypes[BANNER]) { - placement.adFormat = BANNER; - placement.sizes = mediaTypes[BANNER].sizes; - } else if (mediaTypes && mediaTypes[VIDEO]) { - placement.adFormat = VIDEO; - placement.playerSize = mediaTypes[VIDEO].playerSize; - placement.minduration = mediaTypes[VIDEO].minduration; - placement.maxduration = mediaTypes[VIDEO].maxduration; - placement.mimes = mediaTypes[VIDEO].mimes; - placement.protocols = mediaTypes[VIDEO].protocols; - placement.startdelay = mediaTypes[VIDEO].startdelay; - placement.placement = mediaTypes[VIDEO].placement; - placement.skip = mediaTypes[VIDEO].skip; - placement.skipafter = mediaTypes[VIDEO].skipafter; - placement.minbitrate = mediaTypes[VIDEO].minbitrate; - placement.maxbitrate = mediaTypes[VIDEO].maxbitrate; - placement.delivery = mediaTypes[VIDEO].delivery; - placement.playbackmethod = mediaTypes[VIDEO].playbackmethod; - placement.api = mediaTypes[VIDEO].api; - placement.linearity = mediaTypes[VIDEO].linearity; - } else if (mediaTypes && mediaTypes[NATIVE]) { - placement.native = mediaTypes[NATIVE]; - placement.adFormat = NATIVE; - } - - return placement; -} - -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidfloor', 0); - } - - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (err) { - logError(err); - return 0; - } -} - export const spec = { code: BIDDER_CODE, supportedMediaTypes: [BANNER, VIDEO, NATIVE], - isBidRequestValid: (bid = {}) => { - const { params, bidId, mediaTypes } = bid; - let valid = Boolean(bidId && params && (params.placementId || params.endpointId)); - - if (mediaTypes && mediaTypes[BANNER]) { - valid = valid && Boolean(mediaTypes[BANNER] && mediaTypes[BANNER].sizes); - } else if (mediaTypes && mediaTypes[VIDEO]) { - valid = valid && Boolean(mediaTypes[VIDEO] && mediaTypes[VIDEO].playerSize); - } else if (mediaTypes && mediaTypes[NATIVE]) { - valid = valid && Boolean(mediaTypes[NATIVE]); - } else { - valid = false; - } - return valid; - }, - - buildRequests: (validBidRequests = [], bidderRequest = {}) => { - // convert Native ORTB definition to old-style prebid native definition - validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); - - let deviceWidth = 0; - let deviceHeight = 0; - - let winLocation; - try { - const winTop = window.top; - deviceWidth = winTop.screen.width; - deviceHeight = winTop.screen.height; - winLocation = winTop.location; - } catch (e) { - logMessage(e); - winLocation = window.location; - } - - const refferUrl = bidderRequest.refererInfo && bidderRequest.refererInfo.page; - let refferLocation; - try { - refferLocation = refferUrl && new URL(refferUrl); - } catch (e) { - logMessage(e); - } - // TODO: does the fallback make sense here? - 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: bidderRequest.timeout - }; - - 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 - }]; - } + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) }; registerBidder(spec); diff --git a/modules/visxBidAdapter.js b/modules/visxBidAdapter.js index beffcf5da95..217cab48cee 100644 --- a/modules/visxBidAdapter.js +++ b/modules/visxBidAdapter.js @@ -1,10 +1,11 @@ -import {deepAccess, logError, parseSizesInput, triggerPixel} from '../src/utils.js'; +import {deepAccess, logError, mergeDeep, parseSizesInput, sizeTupleToRtbSize, sizesToSizeTuples, triggerPixel} from '../src/utils.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; import {config} from '../src/config.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {INSTREAM as VIDEO_INSTREAM} from '../src/video.js'; import {getStorageManager} from '../src/storageManager.js'; import {getGptSlotInfoForAdUnitCode} from '../libraries/gptUtils/gptUtils.js'; +import { getBidFromResponse } from '../libraries/processResponse/index.js'; const BIDDER_CODE = 'visx'; const GVLID = 154; @@ -32,7 +33,7 @@ const LOG_ERROR_MESS = { onlyVideoInstream: `Only video ${VIDEO_INSTREAM} supported`, videoMissing: 'Bid request videoType property is missing - ' }; -const currencyWhiteList = ['EUR', 'USD', 'GBP', 'PLN']; +const currencyWhiteList = ['EUR', 'USD', 'GBP', 'PLN', 'CHF', 'SEK']; export const storage = getStorageManager({bidderCode: BIDDER_CODE}); const _bidResponseTimeLogged = []; export const spec = { @@ -59,11 +60,17 @@ export const spec = { config.getConfig('currency.adServerCurrency') || DEFAULT_CUR; + let request; let reqId; let payloadSchain; let payloadUserId; let payloadUserEids; let timeout; + let payloadDevice; + let payloadSite; + let payloadRegs; + let payloadContent; + let payloadUser; if (currencyWhiteList.indexOf(currency) === -1) { logError(LOG_ERROR_MESS.notAllowedCurrency + currency); @@ -80,9 +87,7 @@ export const spec = { imp.push(impObj); bidsMap[bid.bidId] = bid; } - const { params: { uid }, schain, userId, userIdAsEids } = bid; - if (!payloadSchain && schain) { payloadSchain = schain; } @@ -93,6 +98,7 @@ export const spec = { if (!payloadUserId && userId) { payloadUserId = userId; } + auids.push(uid); }); @@ -100,10 +106,7 @@ export const spec = { if (bidderRequest) { timeout = bidderRequest.timeout; - if (bidderRequest.refererInfo && bidderRequest.refererInfo.page) { - // TODO: is 'page' the right value here? - payload.u = bidderRequest.refererInfo.page; - } + if (bidderRequest.gdprConsent) { if (bidderRequest.gdprConsent.consentString) { payload.gdpr_consent = bidderRequest.gdprConsent.consentString; @@ -112,6 +115,42 @@ export const spec = { (typeof bidderRequest.gdprConsent.gdprApplies === 'boolean') ? Number(bidderRequest.gdprConsent.gdprApplies) : 1; } + + const { ortb2 } = bidderRequest; + const { device, site, regs, content } = ortb2; + const userOrtb2 = ortb2.user; + let user; + let userReq; + const vads = _getUserId(); + if (payloadUserEids || payload.gdpr_consent || vads) { + user = { + ext: { + ...(payloadUserEids && { eids: payloadUserEids }), + ...(payload.gdpr_consent && { consent: payload.gdpr_consent }), + ...(vads && { vads }) + } + }; + } + if (user) { + userReq = mergeDeep(user, userOrtb2); + } else { + userReq = userOrtb2; + } + if (device) { + payloadDevice = device; + } + if (site) { + payloadSite = site; + } + if (regs) { + payloadRegs = regs; + } + if (content) { + payloadContent = content; + } + if (userReq) { + payloadUser = userReq; + } } const tmax = timeout; @@ -123,29 +162,25 @@ export const spec = { } }; - const vads = _getUserId(); - const user = { - ext: { - ...(payloadUserEids && { eids: payloadUserEids }), - ...(payload.gdpr_consent && { consent: payload.gdpr_consent }), - ...(vads && { vads }) - } - }; - const regs = ('gdpr_applies' in payload) && { - ext: { - gdpr: payload.gdpr_applies - } - }; + if (payloadRegs === undefined) { + payloadRegs = ('gdpr_applies' in payload) && { + ext: { + gdpr: payload.gdpr_applies + } + }; + } - const request = { + request = { id: reqId, imp, tmax, cur: [currency], source, - site: { page: payload.u }, - ...(Object.keys(user.ext).length && { user }), - ...(regs && { regs }) + ...(payloadUser && { user: payloadUser }), + ...(payloadRegs && {regs: payloadRegs}), + ...(payloadDevice && { device: payloadDevice }), + ...(payloadSite && { site: payloadSite }), + ...(payloadContent && { content: payloadContent }), }; return { @@ -170,7 +205,7 @@ export const spec = { if (!errorMessage && serverResponse.seatbid) { serverResponse.seatbid.forEach(respItem => { - _addBidResponse(_getBidFromResponse(respItem), bidsMap, currency, bidResponses); + _addBidResponse(getBidFromResponse(respItem, LOG_ERROR_MESS), bidsMap, currency, bidResponses); }); } if (errorMessage) logError(errorMessage); @@ -240,13 +275,7 @@ function makeBanner(bannerParams) { if (bannerSizes) { const sizes = parseSizesInput(bannerSizes); if (sizes.length) { - const format = sizes.map(size => { - const [ width, height ] = size.split('x'); - const w = parseInt(width, 10); - const h = parseInt(height, 10); - return { w, h }; - }); - + const format = sizesToSizeTuples(bannerSizes).map(sizeTupleToRtbSize); return { format }; } } @@ -286,17 +315,6 @@ function buildImpObject(bid) { } } -function _getBidFromResponse(respItem) { - if (!respItem) { - logError(LOG_ERROR_MESS.emptySeatbid); - } else if (!respItem.bid) { - logError(LOG_ERROR_MESS.hasNoArrayOfBids + JSON.stringify(respItem)); - } else if (!respItem.bid[0]) { - logError(LOG_ERROR_MESS.noBid); - } - return respItem && respItem.bid && respItem.bid[0]; -} - function _addBidResponse(serverBid, bidsMap, currency, bidResponses) { if (!serverBid) return; let errorMessage; diff --git a/modules/waardexBidAdapter.js b/modules/waardexBidAdapter.js index 92b7fc26e4c..c4ca5d299bc 100644 --- a/modules/waardexBidAdapter.js +++ b/modules/waardexBidAdapter.js @@ -147,7 +147,6 @@ const createVideoObject = (videoMediaTypes, videoParams) => { maxduration: getBidIdParameter('maxduration', videoParams) || 500, protocols: getBidIdParameter('protocols', videoParams) || [2, 3, 5, 6], startdelay: getBidIdParameter('startdelay', videoParams) || 0, - placement: getBidIdParameter('placement', videoParams) || videoMediaTypes.context === 'outstream' ? 3 : 1, skip: getBidIdParameter('skip', videoParams) || 1, skipafter: getBidIdParameter('skipafter', videoParams) || 0, minbitrate: getBidIdParameter('minbitrate', videoParams) || 0, diff --git a/modules/weboramaRtdProvider.js b/modules/weboramaRtdProvider.js index 82feec252eb..67822234840 100644 --- a/modules/weboramaRtdProvider.js +++ b/modules/weboramaRtdProvider.js @@ -100,9 +100,7 @@ * @typedef {WeboCtxConf|WeboUserDataConf|SfbxLiteDataConf} CommonConf */ -import { - getGlobal -} from '../src/prebidGlobal.js'; +import { getGlobal } from '../src/prebidGlobal.js'; import { deepAccess, deepClone, @@ -111,28 +109,18 @@ import { isBoolean, isEmpty, isFn, + isNumber, isPlainObject, isStr, - logWarn, mergeDeep, prefixLog, } from '../src/utils.js'; -import { - submodule -} from '../src/hook.js'; -import { - ajax -} from '../src/ajax.js'; -import { - getStorageManager -} from '../src/storageManager.js'; -import { - MODULE_TYPE_RTD -} from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { ajax } from '../src/ajax.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; import adapterManager from '../src/adapterManager.js'; -import { - tryAppendQueryString -} from '../libraries/urlUtils/urlUtils.js'; +import { tryAppendQueryString } from '../libraries/urlUtils/urlUtils.js'; /** @type {string} */ const MODULE_NAME = 'realTimeData'; @@ -167,7 +155,7 @@ const logger = prefixLog('[WeboramaRTD]'); export const storage = getStorageManager({ moduleType: MODULE_TYPE_RTD, - moduleName: SUBMODULE_NAME + moduleName: SUBMODULE_NAME, }); /** @@ -216,10 +204,14 @@ class WeboramaRtdProvider { sendToBidders: true, onData: () => { /* do nothing */ - } + }, }; /** @type {ModuleParams} */ - const moduleParams = Object.assign({}, globalDefaults, moduleConfig?.params || {}); + const moduleParams = Object.assign( + {}, + globalDefaults, + moduleConfig?.params || {} + ); // reset profiles @@ -229,13 +221,24 @@ class WeboramaRtdProvider { const weboCtxRequiredFields = ['token']; - this.#components.WeboCtx.initialized = this.#initSubSection(moduleParams, WEBO_CTX_CONF_SECTION, { - requiredFields: weboCtxRequiredFields, - }); - this.#components.WeboUserData.initialized = this.#initSubSection(moduleParams, WEBO_USER_DATA_CONF_SECTION, { - userConsent: userConsent || {}, - }); - this.#components.SfbxLiteData.initialized = this.#initSubSection(moduleParams, SFBX_LITE_DATA_CONF_SECTION); + this.#components.WeboCtx.initialized = this.#initSubSection( + moduleParams, + WEBO_CTX_CONF_SECTION, + { + requiredFields: weboCtxRequiredFields, + } + ); + this.#components.WeboUserData.initialized = this.#initSubSection( + moduleParams, + WEBO_USER_DATA_CONF_SECTION, + { + userConsent: userConsent || {}, + } + ); + this.#components.SfbxLiteData.initialized = this.#initSubSection( + moduleParams, + SFBX_LITE_DATA_CONF_SECTION + ); return Object.values(this.#components).some((c) => c.initialized); } @@ -264,15 +267,21 @@ class WeboramaRtdProvider { /** @type {WeboCtxConf} */ const weboCtxConf = moduleParams.weboCtxConf || {}; - this.#fetchContextualProfile(weboCtxConf, (data) => { - logger.logMessage('fetchContextualProfile on getBidRequestData is done'); + this.#fetchContextualProfile( + weboCtxConf, + (data) => { + logger.logMessage( + 'fetchContextualProfile on getBidRequestData is done' + ); - this.#setWeboContextualProfile(data); - }, () => { - this.#handleBidRequestData(reqBidsConfigObj, moduleParams); + this.#setWeboContextualProfile(data); + }, + () => { + this.#handleBidRequestData(reqBidsConfigObj, moduleParams); - onDone(); - }); + onDone(); + } + ); } /** @@ -344,17 +353,23 @@ class WeboramaRtdProvider { extra = extra || {}; const requiredFields = extra?.requiredFields || []; - requiredFields.forEach(field => { + requiredFields.forEach((field) => { if (!(field in weboSectionConf)) { throw `missing required field '${field}'`; } }); - if (isPlainObject(extra?.userConsent?.gdpr) && !this.#checkTCFv2(extra.userConsent.gdpr)) { + if ( + isPlainObject(extra?.userConsent?.gdpr) && + !this.#checkTCFv2(extra.userConsent.gdpr) + ) { throw 'gdpr consent not ok'; } } catch (e) { - logger.logError(`unable to initialize: error on '${subSection}' configuration:`, e); + logger.logError( + `unable to initialize: error on '${subSection}' configuration:`, + e + ); return false; } @@ -382,14 +397,18 @@ class WeboramaRtdProvider { return true; } - if (deepAccess(gdpr, 'vendorData.vendor.consents') && - deepAccess(gdpr, 'vendorData.purpose.consents')) { - return gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id + if ( + deepAccess(gdpr, 'vendorData.vendor.consents') && + deepAccess(gdpr, 'vendorData.purpose.consents') + ) { + return ( + gdpr.vendorData.vendor.consents[GVLID] === true && // check weborama vendor id gdpr.vendorData.purpose.consents[1] === true && // info storage access gdpr.vendorData.purpose.consents[3] === true && // create personalized ads gdpr.vendorData.purpose.consents[4] === true && // select personalized ads gdpr.vendorData.purpose.consents[5] === true && // create personalized content - gdpr.vendorData.purpose.consents[6] === true; // select personalized content + gdpr.vendorData.purpose.consents[6] === true + ); // select personalized content } return true; @@ -439,7 +458,9 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #coerceSetPrebidTargeting(submoduleParams) { try { - submoduleParams.setPrebidTargeting = this.#wrapValidatorCallback(submoduleParams.setPrebidTargeting); + submoduleParams.setPrebidTargeting = this.#wrapValidatorCallback( + submoduleParams.setPrebidTargeting + ); } catch (e) { throw `invalid setPrebidTargeting: ${e}`; } @@ -458,10 +479,13 @@ class WeboramaRtdProvider { let sendToBidders = submoduleParams.sendToBidders; if (isPlainObject(sendToBidders)) { - const sendToBiddersMap = Object.entries(sendToBidders).reduce((map, [key, value]) => { - map[key] = this.#wrapValidatorCallback(value); - return map; - }, {}); + const sendToBiddersMap = Object.entries(sendToBidders).reduce( + (map, [key, value]) => { + map[key] = this.#wrapValidatorCallback(value); + return map; + }, + {} + ); submoduleParams.sendToBidders = (bid, adUnitCode) => { const bidder = bid.bidder; @@ -482,8 +506,10 @@ class WeboramaRtdProvider { } try { - submoduleParams.sendToBidders = this.#wrapValidatorCallback(submoduleParams.sendToBidders, - (bid) => bid.bidder); + submoduleParams.sendToBidders = this.#wrapValidatorCallback( + submoduleParams.sendToBidders, + (bid) => bid.bidder + ); } catch (e) { throw `invalid sendToBidders: ${e}`; } @@ -516,9 +542,9 @@ class WeboramaRtdProvider { const adUnits = reqBidsConfigObj.adUnits || getGlobal().adUnits; try { - adUnits.forEach( - adUnit => adUnit.bids?.forEach( - bid => profileHandlers.forEach(ph => { + adUnits.forEach((adUnit) => + adUnit.bids?.forEach((bid) => + profileHandlers.forEach((ph) => { // logger.logMessage(`check if bidder '${bid.bidder}' and adunit '${adUnit.code} are share ${ph.metadata.source} data`); const [data, metadata] = this.#copyDataAndMetadata(ph); @@ -534,12 +560,15 @@ class WeboramaRtdProvider { logger.logError('unable to send data to bidders:', e); } - profileHandlers.forEach(ph => { + profileHandlers.forEach((ph) => { try { const [data, metadata] = this.#copyDataAndMetadata(ph); ph.onData(data, metadata); } catch (e) { - logger.logError(`error while execute onData callback with ${ph.metadata.source}-based data:`, e); + logger.logError( + `error while execute onData callback with ${ph.metadata.source}-based data:`, + e + ); } }); } @@ -569,7 +598,8 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #fetchContextualProfile(weboCtxConf, onSuccess, onDone) { const token = weboCtxConf.token; - const baseURLProfileAPI = weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; + const baseURLProfileAPI = + weboCtxConf.baseURLProfileAPI || BASE_URL_CONTEXTUAL_PROFILE_API; let path = '/profile'; let queryString = ''; @@ -583,7 +613,10 @@ class WeboramaRtdProvider { try { assetID = weboCtxConf.assetID(); } catch (e) { - logger.logError('unexpected error while fetching asset id from callback', e); + logger.logError( + 'unexpected error while fetching asset id from callback', + e + ); onDone(); @@ -660,30 +693,43 @@ class WeboramaRtdProvider { */ // eslint-disable-next-line no-dupe-class-members #buildProfileHandlers(moduleParams) { - const steps = [{ - component: this.#components.WeboCtx, - conf: moduleParams?.weboCtxConf, - }, { - component: this.#components.WeboUserData, - conf: moduleParams?.weboUserDataConf, - }, { - component: this.#components.SfbxLiteData, - conf: moduleParams?.sfbxLiteDataConf, - }]; - - return steps.filter(step => step.component.initialized).reduce((ph, { component, conf }) => { - const user = component.user; - const source = component.source; - const callback = component.callbackBuilder(component /* equivalent to this */); - const profileHandler = this.#buildProfileHandler(conf, callback, user, source); - if (profileHandler) { - ph.push(profileHandler); - } else { - logger.logMessage(`skip ${source} profile: no data`); - } + const steps = [ + { + component: this.#components.WeboCtx, + conf: moduleParams?.weboCtxConf, + }, + { + component: this.#components.WeboUserData, + conf: moduleParams?.weboUserDataConf, + }, + { + component: this.#components.SfbxLiteData, + conf: moduleParams?.sfbxLiteDataConf, + }, + ]; + + return steps + .filter((step) => step.component.initialized) + .reduce((ph, { component, conf }) => { + const user = component.user; + const source = component.source; + const callback = component.callbackBuilder( + component /* equivalent to this */ + ); + const profileHandler = this.#buildProfileHandler( + conf, + callback, + user, + source + ); + if (profileHandler) { + ph.push(profileHandler); + } else { + logger.logMessage(`skip ${source} profile: no data`); + } - return ph; - }, []); + return ph; + }, []); } /** @@ -823,13 +869,22 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #handleBidViaORTB2(reqBidsConfigObj, bidder, profile, metadata) { if (isBoolean(metadata.user)) { - logger.logMessage(`bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd`); + logger.logMessage( + `bidder '${bidder}' is not directly supported, trying set data via bidder ortb2 fpd` + ); const section = metadata.user ? 'user' : 'site'; const path = `${section}.ext.data`; - this.#setBidderOrtb2(reqBidsConfigObj.ortb2Fragments?.bidder, bidder, path, profile) + this.#setBidderOrtb2( + reqBidsConfigObj.ortb2Fragments?.bidder, + bidder, + path, + profile + ); } else { - logger.logMessage(`SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric`); + logger.logMessage( + `SKIP unsupported bidder '${bidder}', data from '${metadata.source}' is not defined as user or site-centric` + ); } } /** @@ -845,7 +900,7 @@ class WeboramaRtdProvider { // eslint-disable-next-line no-dupe-class-members #setBidderOrtb2(bidderOrtb2Fragments, bidder, path, profile) { const base = `${bidder}.${path}`; - this.#assignProfileToObject(bidderOrtb2Fragments, base, profile) + this.#assignProfileToObject(bidderOrtb2Fragments, base, profile); } /** * assign profile to object @@ -861,7 +916,7 @@ class WeboramaRtdProvider { Object.entries(profile).forEach(([key, values]) => { const path = `${base}.${key}`; deepSetValue(destination, path, values); - }) + }); } /** @@ -913,6 +968,7 @@ class WeboramaRtdProvider { /** * check if profile is valid + * a valid profile must be a plain object and every value should be an array of strings or numbers * @param {*} profile * @returns {boolean} */ @@ -921,7 +977,18 @@ export function isValidProfile(profile) { return false; } - return Object.values(profile).every((field) => isArray(field) && field.every(isStr)); + return Object.values(profile).every( + (field) => isArray(field) && field.every(isStrOrNumber) + ); +} + +/** + * Return if the object is a string or number + * @param {*} object object to test + * @return {Boolean} if object is a string or number + */ +function isStrOrNumber(object) { + return isStr(object) || isNumber(object); } /** @@ -943,7 +1010,7 @@ function getContextualProfile(component /* equivalent to this */) { const defaultContextualProfile = weboCtxConf.defaultProfile || {}; return [defaultContextualProfile, true]; - } + }; } /** @@ -958,13 +1025,15 @@ function getWeboUserDataProfile(component /* equivalent to this */) { * @returns {[Profile,boolean]} weboUserData profile + isDefault boolean flag */ return function (weboUserDataConf) { - return getDataFromLocalStorage(weboUserDataConf, + return getDataFromLocalStorage( + weboUserDataConf, () => component.data, - (data) => component.data = data, + (data) => (component.data = data), DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY, LOCAL_STORAGE_USER_TARGETING_SECTION, - WEBO_USER_DATA_SOURCE_LABEL); - } + WEBO_USER_DATA_SOURCE_LABEL + ); + }; } /** @@ -979,13 +1048,15 @@ function getSfbxLiteDataProfile(component /* equivalent to this */) { * @returns {[Profile,boolean]} sfbxLiteData profile + isDefault boolean flag */ return function getSfbxLiteDataProfile(sfbxLiteDataConf) { - return getDataFromLocalStorage(sfbxLiteDataConf, + return getDataFromLocalStorage( + sfbxLiteDataConf, () => component.data, - (data) => component.data = data, + (data) => (component.data = data), DEFAULT_LOCAL_STORAGE_LITE_PROFILE_KEY, LOCAL_STORAGE_LITE_TARGETING_SECTION, - SFBX_LITE_DATA_SOURCE_LABEL); - } + SFBX_LITE_DATA_SOURCE_LABEL + ); + }; } /** @@ -1008,11 +1079,23 @@ function getSfbxLiteDataProfile(component /* equivalent to this */) { * @param {string} source * @returns {[Profile,boolean]} webo (user|lite) data profile + isDefault boolean flag */ -function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalStorageProfileKey, targetingSection, source) { +function getDataFromLocalStorage( + weboDataConf, + cacheGet, + cacheSet, + defaultLocalStorageProfileKey, + targetingSection, + source +) { const defaultProfile = weboDataConf.defaultProfile || {}; - if (storage.hasLocalStorage() && storage.localStorageIsEnabled() && !cacheGet()) { - const localStorageProfileKey = weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; + if ( + storage.hasLocalStorage() && + storage.localStorageIsEnabled() && + !cacheGet() + ) { + const localStorageProfileKey = + weboDataConf.localStorageProfileKey || defaultLocalStorageProfileKey; const entry = storage.getDataFromLocalStorage(localStorageProfileKey); if (entry) { @@ -1022,9 +1105,12 @@ function getDataFromLocalStorage(weboDataConf, cacheGet, cacheSet, defaultLocalS const profile = data[targetingSection]; const valid = isValidProfile(profile); if (!valid) { - logWarn(`found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}`); + logger.logMessage( + `WARNING: found invalid ${source} profile on local storage key ${localStorageProfileKey}, section ${targetingSection}: `, + profile + ); - return; + return [defaultProfile, true]; } if (!isEmpty(data)) { diff --git a/modules/weboramaRtdProvider.md b/modules/weboramaRtdProvider.md index a8fc692ba74..11b4368a502 100644 --- a/modules/weboramaRtdProvider.md +++ b/modules/weboramaRtdProvider.md @@ -67,7 +67,7 @@ pbjs.setConfig({ enabled: true, }, sfbxLiteDataConf: { // sfbx-lite site-centric configuration, *omit if not needed* - enabled: true, + enabled: true, }, } }, @@ -215,7 +215,7 @@ A better look on the `Object` type ```javascript sendToBidders: { appnexus: true, // send profile to appnexus on all ad units - pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units + pubmatic: ['adUnitCode1'],// send profile to pubmatic on this ad units } ``` @@ -253,7 +253,7 @@ To be possible customize the way we send data to bidders via this callback: sendToBidders: function(bid, adUnitCode, data, metadata){ if (bid.bidder == 'other'){ /* use bid object to store data based on this specific logic, like in the example below */ - + bid.params = bid.params || {}; bid.params['some_specific_key'] = data; @@ -374,7 +374,7 @@ pbjs.que.push(function () { sendToBidders: true, // override param.sendToBidders. default is true defaultProfile: { // optional, used if nothing is found webo_cs: [...], // wam custom segments - webo_audiences: [...], // wam audiences + webo_audiences: [...], // wam audiences }, enabled: true, }, @@ -485,18 +485,18 @@ pbjs.que.push(function () { params: { weboCtxConf: { token: "to-be-defined", // mandatory - setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits sendToBidders: ['appnexus',...], // overide, send to only some bidders enabled: true, }, weboUserDataConf: { accountId: 12345, // recommended - setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode2',...], // set target only on certain adunits sendToBidders: ['rubicon',...], // overide, send to only some bidders enabled: true, }, sfbxLiteDataConf: { - setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode3',...], // set target only on certain adunits sendToBidders: ['smartadserver',...], // overide, send to only some bidders enabled: true, } @@ -546,15 +546,15 @@ pbjs.que.push(function () { }, weboUserDataConf: { accountId: 12345, // recommended - setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits + setPrebidTargeting: ['adUnitCode1',...], // set target only on certain adunits sendToBidders: { // send to only some bidders and adunits - 'appnexus': true, // all adunits for appnexus + 'appnexus': true, // all adunits for appnexus 'pubmatic': ['adUnitCode1',...] // some adunits for pubmatic // other bidders will be ignored }, defaultProfile: { // optional - webo_cs: ['Red'], - webo_audiences: ['bam'] + webo_cs: ['Red'], // using label + webo_audiences: [12345] // using id }, localStorageProfileKey: 'webo_wam2gam_entry', // default enabled: true, diff --git a/modules/widespaceBidAdapter.js b/modules/widespaceBidAdapter.js index ea6f1bce793..6fa14f5d68c 100644 --- a/modules/widespaceBidAdapter.js +++ b/modules/widespaceBidAdapter.js @@ -1,6 +1,6 @@ import {config} from '../src/config.js'; import {registerBidder} from '../src/adapters/bidderFactory.js'; -import {parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; +import {deepClone, parseQueryStringParameters, parseSizesInput} from '../src/utils.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; @@ -212,7 +212,7 @@ function getLcuid() { } function encodedParamValue(value) { - const requiredStringify = typeof JSON.parse(JSON.stringify(value)) === 'object'; + const requiredStringify = typeof deepClone(value) === 'object'; return encodeURIComponent(requiredStringify ? JSON.stringify(value) : value); } diff --git a/modules/winrBidAdapter.js b/modules/winrBidAdapter.js index 42447eaac80..ed0d5ddd905 100644 --- a/modules/winrBidAdapter.js +++ b/modules/winrBidAdapter.js @@ -13,9 +13,10 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER} from '../src/mediaTypes.js'; import {find, includes} from '../src/polyfill.js'; import {getStorageManager} from '../src/storageManager.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; import {getANKeywordParam} from '../libraries/appnexusUtils/anKeywords.js'; import {convertCamelToUnderscore} from '../libraries/appnexusUtils/anUtils.js'; +import { transformSizes } from '../libraries/sizeUtils/tranformSize.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -461,7 +462,7 @@ function bidToTag(bid) { } tag.keywords = getANKeywordParam(bid.ortb2, bid.params.keywords) - let gpid = deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); + let gpid = deepAccess(bid, 'ortb2Imp.ext.gpid') || deepAccess(bid, 'ortb2Imp.ext.data.pbadslot'); if (gpid) { tag.gpid = gpid; } @@ -475,32 +476,6 @@ function bidToTag(bid) { return tag; } -/* Turn bid request sizes into ut-compatible format */ -function transformSizes(requestSizes) { - let sizes = []; - let sizeObj = {}; - - if ( - isArray(requestSizes) && - requestSizes.length === 2 && - !isArray(requestSizes[0]) - ) { - sizeObj.width = parseInt(requestSizes[0], 10); - sizeObj.height = parseInt(requestSizes[1], 10); - sizes.push(sizeObj); - } else if (typeof requestSizes === 'object') { - for (let i = 0; i < requestSizes.length; i++) { - let size = requestSizes[i]; - sizeObj = {}; - sizeObj.width = parseInt(size[0], 10); - sizeObj.height = parseInt(size[1], 10); - sizes.push(sizeObj); - } - } - - return sizes; -} - function hasUserInfo(bid) { return !!bid.params.user; } diff --git a/modules/wurflRtdProvider.js b/modules/wurflRtdProvider.js new file mode 100644 index 00000000000..dbf50744d25 --- /dev/null +++ b/modules/wurflRtdProvider.js @@ -0,0 +1,286 @@ +import { submodule } from '../src/hook.js'; +import { fetch, sendBeacon } from '../src/ajax.js'; +import { loadExternalScript } from '../src/adloader.js'; +import { + mergeDeep, + prefixLog, +} from '../src/utils.js'; +import { MODULE_TYPE_RTD } from '../src/activities/modules.js'; + +// Constants +const REAL_TIME_MODULE = 'realTimeData'; +const MODULE_NAME = 'wurfl'; + +// WURFL_JS_HOST is the host for the WURFL service endpoints +const WURFL_JS_HOST = 'https://prebid.wurflcloud.com'; +// WURFL_JS_ENDPOINT_PATH is the path for the WURFL.js endpoint used to load WURFL data +const WURFL_JS_ENDPOINT_PATH = '/wurfl.js'; +// STATS_ENDPOINT_PATH is the path for the stats endpoint used to send analytics data +const STATS_ENDPOINT_PATH = '/v1/prebid/stats'; + +const logger = prefixLog('[WURFL RTD Submodule]'); + +// enrichedBidders holds a list of prebid bidder names, of bidders which have been +// injected with WURFL data +const enrichedBidders = new Set(); + +/** + * init initializes the WURFL RTD submodule + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +const init = (config, userConsent) => { + logger.logMessage('initialized'); + return true; +} + +/** + * getBidRequestData enriches the OpenRTB 2.0 device data with WURFL data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Function} callback Called on completion + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +const getBidRequestData = (reqBidsConfigObj, callback, config, userConsent) => { + const altHost = config.params?.altHost ?? null; + const isDebug = config.params?.debug ?? false; + + const bidders = new Set(); + reqBidsConfigObj.adUnits.forEach(adUnit => { + adUnit.bids.forEach(bid => { + bidders.add(bid.bidder); + }); + }); + + let host = WURFL_JS_HOST; + if (altHost) { + host = altHost; + } + + const url = new URL(host); + url.pathname = WURFL_JS_ENDPOINT_PATH; + + if (isDebug) { + url.searchParams.set('debug', 'true') + } + + url.searchParams.set('mode', 'prebid') + logger.logMessage('url', url.toString()); + + try { + loadExternalScript(url.toString(), MODULE_TYPE_RTD, MODULE_NAME, () => { + logger.logMessage('script injected'); + window.WURFLPromises.complete.then((res) => { + logger.logMessage('received data', res); + if (!res.wurfl_pbjs) { + logger.logError('invalid WURFL.js for Prebid response'); + } else { + enrichBidderRequests(reqBidsConfigObj, bidders, res); + } + callback(); + }); + }); + } catch (err) { + logger.logError(err); + callback(); + } +} + +/** + * enrichBidderRequests enriches the OpenRTB 2.0 device data with WURFL data for Business Edition + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {Array} bidders List of bidders + * @param {Object} wjsResponse WURFL.js response + */ +function enrichBidderRequests(reqBidsConfigObj, bidders, wjsResponse) { + const authBidders = wjsResponse.wurfl_pbjs?.authorized_bidders ?? {}; + const caps = wjsResponse.wurfl_pbjs?.caps ?? []; + + bidders.forEach((bidderCode) => { + if (bidderCode in authBidders) { + // inject WURFL data + enrichedBidders.add(bidderCode); + const data = bidderData(wjsResponse.WURFL, caps, authBidders[bidderCode]); + data['enrich_device'] = true; + enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + return; + } + // inject WURFL low entropy data + const data = lowEntropyData(wjsResponse.WURFL, wjsResponse.wurfl_pbjs?.low_entropy_caps); + enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + }); +} + +/** + * bidderData returns the WURFL data for a bidder + * @param {Object} wurflData WURFL data + * @param {Array} caps Capability list + * @param {Array} filter Filter list + * @returns {Object} Bidder data + */ +export const bidderData = (wurflData, caps, filter) => { + const data = {}; + caps.forEach((cap, index) => { + if (!filter.includes(index)) { + return; + } + if (cap in wurflData) { + data[cap] = wurflData[cap]; + } + }); + return data; +} + +/** + * lowEntropyData returns the WURFL low entropy data + * @param {Object} wurflData WURFL data + * @param {Array} lowEntropyCaps Low entropy capability list + * @returns {Object} Bidder data + */ +export const lowEntropyData = (wurflData, lowEntropyCaps) => { + const data = {}; + lowEntropyCaps.forEach((cap, _) => { + let value = wurflData[cap]; + if (cap == 'complete_device_name') { + value = value.replace(/Apple (iP(hone|ad|od)).*/, 'Apple iP$2'); + } + data[cap] = value; + }); + if ('model_name' in wurflData) { + data['model_name'] = wurflData.model_name.replace(/(iP(hone|ad|od)).*/, 'iP$2'); + } + if ('brand_name' in wurflData) { + data['brand_name'] = wurflData.brand_name; + } + return data; +} +/** + * enrichBidderRequest enriches the bidder request with WURFL data + * @param {Object} reqBidsConfigObj Bid request configuration object + * @param {String} bidderCode Bidder code + * @param {Object} wurflData WURFL data + */ +export const enrichBidderRequest = (reqBidsConfigObj, bidderCode, wurflData) => { + const ortb2data = { + 'device': { + 'ext': {}, + }, + }; + + const device = reqBidsConfigObj.ortb2Fragments.global.device; + enrichOrtb2DeviceData('make', wurflData.brand_name, device, ortb2data); + enrichOrtb2DeviceData('model', wurflData.model_name, device, ortb2data); + if (wurflData.enrich_device) { + delete wurflData.enrich_device; + enrichOrtb2DeviceData('devicetype', makeOrtb2DeviceType(wurflData), device, ortb2data); + enrichOrtb2DeviceData('os', wurflData.advertised_device_os, device, ortb2data); + enrichOrtb2DeviceData('osv', wurflData.advertised_device_os_version, device, ortb2data); + enrichOrtb2DeviceData('hwv', wurflData.model_name, device, ortb2data); + enrichOrtb2DeviceData('h', wurflData.resolution_height, device, ortb2data); + enrichOrtb2DeviceData('w', wurflData.resolution_width, device, ortb2data); + enrichOrtb2DeviceData('ppi', wurflData.pixel_density, device, ortb2data); + enrichOrtb2DeviceData('pxratio', wurflData.density_class, device, ortb2data); + enrichOrtb2DeviceData('js', wurflData.ajax_support_javascript, device, ortb2data); + } + ortb2data.device.ext['wurfl'] = wurflData + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data }); +} + +/** + * makeOrtb2DeviceType returns the ortb2 device type based on WURFL data + * @param {Object} wurflData WURFL data + * @returns {Number} ortb2 device type + * @see https://www.scientiamobile.com/how-to-populate-iab-openrtb-device-object/ + */ +export function makeOrtb2DeviceType(wurflData) { + if (wurflData.is_mobile) { + if (!('is_phone' in wurflData) || !('is_tablet' in wurflData)) { + return undefined; + } + if (wurflData.is_phone || wurflData.is_tablet) { + return 1; + } + return 6; + } + if (wurflData.is_full_desktop) { + return 2; + } + if (wurflData.is_connected_tv) { + return 3; + } + if (wurflData.is_phone) { + return 4; + } + if (wurflData.is_tablet) { + return 5; + } + if (wurflData.is_ott) { + return 7; + } + return undefined; +} + +/** + * enrichOrtb2DeviceData enriches the ortb2data device data with WURFL data. + * Note: it does not overrides properties set by Prebid.js + * @param {String} key the device property key + * @param {any} value the value of the device property + * @param {Object} device the ortb2 device object from Prebid.js + * @param {Object} ortb2data the ortb2 device data enrchiced with WURFL data + */ +function enrichOrtb2DeviceData(key, value, device, ortb2data) { + if (device?.[key] !== undefined) { + // value already defined by Prebid.js, do not overrides + return; + } + if (value === undefined) { + return; + } + ortb2data.device[key] = value; +} + +/** + * onAuctionEndEvent is called when the auction ends + * @param {Object} auctionDetails Auction details + * @param {Object} config Configuration for WURFL RTD submodule + * @param {Object} userConsent User consent data + */ +function onAuctionEndEvent(auctionDetails, config, userConsent) { + const altHost = config.params?.altHost ?? null; + + let host = WURFL_JS_HOST; + if (altHost) { + host = altHost; + } + + const url = new URL(host); + url.pathname = STATS_ENDPOINT_PATH; + + if (enrichedBidders.size === 0) { + return; + } + + var payload = JSON.stringify({ bidders: [...enrichedBidders] }); + const sentBeacon = sendBeacon(url.toString(), payload); + if (sentBeacon) { + return; + } + + fetch(url.toString(), { + method: 'POST', + body: payload, + mode: 'no-cors', + keepalive: true + }); +} + +// The WURFL submodule +export const wurflSubmodule = { + name: MODULE_NAME, + init, + getBidRequestData, + onAuctionEndEvent, +} + +// Register the WURFL submodule as submodule of realTimeData +submodule(REAL_TIME_MODULE, wurflSubmodule); diff --git a/modules/wurflRtdProvider.md b/modules/wurflRtdProvider.md new file mode 100644 index 00000000000..d656add3543 --- /dev/null +++ b/modules/wurflRtdProvider.md @@ -0,0 +1,67 @@ +# WURFL Real-time Data Submodule + +## Overview + + Module Name: WURFL Rtd Provider + Module Type: Rtd Provider + Maintainer: prebid@scientiamobile.com + +## Description + +The WURFL RTD module enriches the OpenRTB 2.0 device data with [WURFL data](https://www.scientiamobile.com/wurfl-js-business-edition-at-the-intersection-of-javascript-and-enterprise/). +The module sets the WURFL data in `device.ext.wurfl` and all the bidder adapters will always receive the low entry capabilites like `is_mobile`, `complete_device_name` and `form_factor`. + +For a more detailed analysis bidders can subscribe to detect iPhone and iPad models and receive additional [WURFL device capabilities](https://www.scientiamobile.com/capabilities/?products%5B%5D=wurfl-js). + +## User-Agent Client Hints + +WURFL.js is fully compatible with Chromium's User-Agent Client Hints (UA-CH) initiative. If User-Agent Client Hints are absent in the HTTP headers that WURFL.js receives, the service will automatically fall back to using the User-Agent Client Hints' JS API to fetch [high entropy client hint values](https://wicg.github.io/ua-client-hints/#getHighEntropyValues) from the client device. However, we recommend that you explicitly opt-in/advertise support for User-Agent Client Hints on your website and delegate them to the WURFL.js service for the fastest detection experience. Our documentation regarding implementing User-Agent Client Hint support [is available here](https://docs.scientiamobile.com/guides/implementing-useragent-clienthints). + +## Usage + +### Build +``` +gulp build --modules="wurflRtdProvider,appnexusBidAdapter,..." +``` + +### Configuration + +Use `setConfig` to instruct Prebid.js to initilize the WURFL RTD module, as specified below. + +This module is configured as part of the `realTimeData.dataProviders` + +```javascript +var TIMEOUT = 1000; +pbjs.setConfig({ + realTimeData: { + auctionDelay: TIMEOUT, + dataProviders: [{ + name: 'wurfl', + waitForIt: true, + params: { + debug: false + } + }] + } +}); +``` + +### Parameters + +| Name | Type | Description | Default | +| :------------------------ | :------------ | :--------------------------------------------------------------- |:----------------- | +| name | String | Real time data module name | Always 'wurfl' | +| waitForIt | Boolean | Should be `true` if there's an `auctionDelay` defined (optional) | `false` | +| params | Object | | | +| params.altHost | String | Alternate host to connect to WURFL.js | | +| params.debug | Boolean | Enable debug | `false` | + +## Testing + +To view an example of how the WURFL RTD module works : + +`gulp serve --modules=wurflRtdProvider,appnexusBidAdapter` + +and then point your browser at: + +`http://localhost:9999/integrationExamples/gpt/wurflRtdProvider_example.html` diff --git a/modules/xeBidAdapter.js b/modules/xeBidAdapter.js index a813b9aa2a3..9bd6f8adbac 100644 --- a/modules/xeBidAdapter.js +++ b/modules/xeBidAdapter.js @@ -1,214 +1,16 @@ -import { config } from '../src/config.js'; -import { BANNER, VIDEO } from '../src/mediaTypes.js'; -import { registerBidder } from '../src/adapters/bidderFactory.js'; -import {parseSizesInput, isFn, deepAccess, logError, isArray, getBidIdParameter} from '../src/utils.js'; -import {getAdUnitSizes} from '../libraries/sizeUtils/sizeUtils.js'; +import {BANNER, VIDEO} from '../src/mediaTypes.js'; +import {registerBidder} from '../src/adapters/bidderFactory.js'; +import {buildRequests, getUserSyncs, interpretResponse, isBidRequestValid} from '../libraries/xeUtils/bidderUtils.js'; -/** - * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest - * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid - * @typedef {import('../src/adapters/bidderFactory.js').ServerResponse} ServerResponse - * @typedef {import('../src/adapters/bidderFactory.js').SyncOptions} SyncOptions - * @typedef {import('../src/adapters/bidderFactory.js').UserSync} UserSync - */ - -const CUR = 'USD'; const BIDDER_CODE = 'xe'; const ENDPOINT = 'https://pbjs.xe.works/bid'; -/** - * 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. - */ -function isBidRequestValid(req) { - if (req && typeof req.params !== 'object') { - logError('Params is not defined or is incorrect in the bidder settings'); - return false; - } - - if (!getBidIdParameter('env', req.params) || !getBidIdParameter('placement', req.params)) { - logError('Env or placement is not present in bidder params'); - return false; - } - - if (deepAccess(req, 'mediaTypes.video') && !isArray(deepAccess(req, 'mediaTypes.video.playerSize'))) { - logError('mediaTypes.video.playerSize is required for video'); - return false; - } - - return true; -} - -/** - * Make a server request from the list of BidRequests. - * - * @param {validBidRequest?pbjs_debug=trues[]} - an array of bids - * @return ServerRequest Info describing the request to the server. - */ -function buildRequests(validBidRequests, bidderRequest) { - const { refererInfo = {}, gdprConsent = {}, uspConsent } = bidderRequest; - const requests = validBidRequests.map(req => { - const request = {}; - request.bidId = req.bidId; - request.banner = deepAccess(req, 'mediaTypes.banner'); - // TODO: fix auctionId leak: https://github.com/prebid/Prebid.js/issues/9781 - request.auctionId = req.auctionId; - request.transactionId = req.ortb2Imp?.ext?.tid; - request.sizes = parseSizesInput(getAdUnitSizes(req)); - request.schain = req.schain; - request.location = { - page: refererInfo.page, - location: refererInfo.location, - domain: refererInfo.domain, - whost: window.location.host, - ref: refererInfo.ref, - isAmp: refererInfo.isAmp - }; - request.device = { - ua: navigator.userAgent, - lang: navigator.language - }; - request.env = { - env: req.params.env, - placement: req.params.placement - }; - request.ortb2 = req.ortb2; - request.ortb2Imp = req.ortb2Imp; - request.tz = new Date().getTimezoneOffset(); - request.ext = req.params.ext; - request.bc = req.bidRequestsCount; - request.floor = getBidFloor(req); - - if (req.userIdAsEids && req.userIdAsEids.length !== 0) { - request.userEids = req.userIdAsEids; - } else { - request.userEids = []; - } - if (gdprConsent.gdprApplies) { - request.gdprApplies = Number(gdprConsent.gdprApplies); - request.consentString = gdprConsent.consentString; - } else { - request.gdprApplies = 0; - request.consentString = ''; - } - if (uspConsent) { - request.usPrivacy = uspConsent; - } else { - request.usPrivacy = ''; - } - if (config.getConfig('coppa')) { - request.coppa = 1; - } else { - request.coppa = 0; - } - - const video = deepAccess(req, 'mediaTypes.video'); - if (video) { - request.sizes = parseSizesInput(deepAccess(req, 'mediaTypes.video.playerSize')); - request.video = video; - } - - return request; - }); - - return { - method: 'POST', - url: ENDPOINT, - data: JSON.stringify(requests), - withCredentials: true, - bidderRequest, - 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. - */ -function interpretResponse(serverResponse, { bidderRequest }) { - const response = []; - if (!isArray(deepAccess(serverResponse, 'body.data'))) { - return response; - } - - serverResponse.body.data.forEach(serverBid => { - const bid = { - requestId: bidderRequest.bidId, - dealId: bidderRequest.dealId || null, - ...serverBid - }; - response.push(bid); - }); - - return response; -} - -/** - * 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. - */ -function getUserSyncs(syncOptions, serverResponses, gdprConsent = {}, uspConsent = '') { - const syncs = []; - const pixels = deepAccess(serverResponses, '0.body.data.0.ext.pixels'); - - if ((syncOptions.iframeEnabled || syncOptions.pixelEnabled) && isArray(pixels) && pixels.length !== 0) { - const gdprFlag = `&gdpr=${gdprConsent.gdprApplies ? 1 : 0}`; - const gdprString = `&gdpr_consent=${encodeURIComponent((gdprConsent.consentString || ''))}`; - const usPrivacy = `us_privacy=${encodeURIComponent(uspConsent)}`; - - pixels.forEach(pixel => { - const [ type, url ] = pixel; - const sync = { type, url: `${url}&${usPrivacy}${gdprFlag}${gdprString}` }; - if (type === 'iframe' && syncOptions.iframeEnabled) { - syncs.push(sync) - } else if (type === 'image' && syncOptions.pixelEnabled) { - syncs.push(sync) - } - }); - } - - return syncs; -} - -/** - * Get valid floor value from getFloor fuction. - * - * @param {Object} bid Current bid request. - * @return {null|Number} Returns floor value when bid.getFloor is function and returns valid floor object with USD currency, otherwise returns null. - */ -export function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return null; - } - - let floor = bid.getFloor({ - currency: CUR, - mediaType: '*', - size: '*' - }); - - if (typeof floor === 'object' && !isNaN(floor.floor) && floor.currency === CUR) { - return floor.floor; - } - - return null; -} - export const spec = { code: BIDDER_CODE, - aliases: [ 'xeworks', 'lunamediax' ], - supportedMediaTypes: [ BANNER, VIDEO ], + aliases: ['xeworks', 'lunamediax'], + supportedMediaTypes: [BANNER, VIDEO], isBidRequestValid, - buildRequests, + buildRequests: (validBidRequests, bidderRequest) => buildRequests(validBidRequests, bidderRequest, ENDPOINT), interpretResponse, getUserSyncs } diff --git a/modules/yahoosspBidAdapter.js b/modules/yahooAdsBidAdapter.js similarity index 97% rename from modules/yahoosspBidAdapter.js rename to modules/yahooAdsBidAdapter.js index 0453350a85a..7622a5b7587 100644 --- a/modules/yahoosspBidAdapter.js +++ b/modules/yahooAdsBidAdapter.js @@ -3,12 +3,15 @@ import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { deepAccess, isFn, isStr, isNumber, isArray, isEmpty, isPlainObject, generateUUID, logInfo, logWarn } from '../src/utils.js'; import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; -import {hasPurpose1Consent} from '../src/utils/gpdr.js'; +import {hasPurpose1Consent} from '../src/utils/gdpr.js'; const INTEGRATION_METHOD = 'prebid.js'; const BIDDER_CODE = 'yahooAds'; -const BIDDER_ALIASES = ['yahoossp', 'yahooAdvertising'] const GVLID = 25; +const BIDDER_ALIASES = [ + { code: 'yahoossp', gvlid: GVLID }, + { code: 'yahooAdvertising', gvlid: GVLID } +]; const ADAPTER_VERSION = '1.1.0'; const PREBID_VERSION = '$prebid.version$'; const DEFAULT_BID_TTL = 300; @@ -45,7 +48,6 @@ const SUPPORTED_USER_ID_SOURCES = [ 'neustar.biz', 'nextroll.com', 'novatiq.com', - 'parrable.com', 'pubcid.org', 'quantcast.com', 'tapad.com', @@ -148,7 +150,7 @@ function getSupportedEids(bid) { } function isSecure(bid) { - return deepAccess(bid, 'params.bidOverride.imp.secure') || (document.location.protocol === 'https:') ? 1 : 0; + return deepAccess(bid, 'params.bidOverride.imp.secure') ?? bid.ortb2Imp?.secure ?? 1; }; function getPubIdMode(bid) { @@ -344,7 +346,7 @@ function appendImpObject(bid, openRtbObject) { const impObject = { id: bid.bidId, secure: isSecure(bid), - bidfloor: getFloorModuleData(bid).floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor') + bidfloor: getFloorModuleData(bid)?.floor || deepAccess(bid, 'params.bidOverride.imp.bidfloor') }; if (bid.mediaTypes.banner && (typeof mediaTypeMode === 'undefined' || mediaTypeMode === BANNER || mediaTypeMode === '*')) { @@ -371,6 +373,7 @@ function appendImpObject(bid, openRtbObject) { pos: deepAccess(bid, 'params.bidOverride.imp.video.pos') || bid.mediaTypes.video.pos || undefined, playbackmethod: deepAccess(bid, 'params.bidOverride.imp.video.playbackmethod') || bid.mediaTypes.video.playbackmethod || undefined, placement: deepAccess(bid, 'params.bidOverride.imp.video.placement') || bid.mediaTypes.video.placement || undefined, + plcmt: deepAccess(bid, 'params.bidOverride.imp.video.plcmt') || bid.mediaTypes.video.plcmt || undefined, linearity: deepAccess(bid, 'params.bidOverride.imp.video.linearity') || bid.mediaTypes.video.linearity || 1, protocols: deepAccess(bid, 'params.bidOverride.imp.video.protocols') || bid.mediaTypes.video.protocols || [2, 5], startdelay: deepAccess(bid, 'params.bidOverride.imp.video.startdelay') || bid.mediaTypes.video.startdelay || 0, @@ -657,13 +660,9 @@ export const spec = { bidResponse.mediaType = VIDEO; bidResponse.meta.mediaType = VIDEO; bidResponse.vastXml = bid.adm; - - if (bid.nurl) { - bidResponse.vastUrl = bid.nurl; - }; } - if (deepAccess(bidderRequest, 'mediaTypes.video.context') === 'outstream' && !bidderRequest.renderer) { + if (deepAccess(bidderRequest, 'mediaTypes.video.context') === 'outstream' && !bidderRequest.renderer && bidResponse.mediaType === VIDEO) { bidResponse.renderer = createRenderer(bidderRequest, bidResponse) || undefined; } diff --git a/modules/yahoosspBidAdapter.md b/modules/yahooAdsBidAdapter.md similarity index 99% rename from modules/yahoosspBidAdapter.md rename to modules/yahooAdsBidAdapter.md index 62fe0f22a55..df9b71b2314 100644 --- a/modules/yahoosspBidAdapter.md +++ b/modules/yahooAdsBidAdapter.md @@ -581,6 +581,7 @@ Currently the bidOverride object only accepts the following: * pos * playbackmethod * placement + * plcmt * linearity * protocols * rewarded @@ -619,6 +620,7 @@ const adUnits = [{ pos: 1, playbackmethod: 0, placement: 1, + plcmt: 1, linearity: 1, protocols: [2,5], startdelay: 0, diff --git a/modules/yandexAnalyticsAdapter.js b/modules/yandexAnalyticsAdapter.js index 8afe7298c13..6c44bea7cd2 100644 --- a/modules/yandexAnalyticsAdapter.js +++ b/modules/yandexAnalyticsAdapter.js @@ -3,6 +3,7 @@ import adapterManager from '../src/adapterManager.js'; import { logError, logInfo } from '../src/utils.js'; import { EVENTS } from '../src/constants.js'; import * as events from '../src/events.js'; +import { getGlobal } from '../src/prebidGlobal.js'; const timeoutIds = {}; const tryUntil = (operationId, conditionCb, cb) => { @@ -23,6 +24,7 @@ const clearTryUntilTimeouts = (timeouts) => { }); }; +export const PBJS_INIT_EVENT_NAME = 'pbjsInit'; const SEND_EVENTS_BUNDLE_TIMEOUT = 1500; const { BID_REQUESTED, @@ -122,6 +124,9 @@ const yandexAnalytics = Object.assign(buildAdapter({ analyticsType: 'endpoint' } logError('Aborting yandex analytics provider initialization.'); }, 25000); + yandexAnalytics.onEvent(PBJS_INIT_EVENT_NAME, { + 'version': getGlobal().version, + }); events.getEvents().forEach((event) => { if (event && EVENTS_TO_TRACK.indexOf(event.eventType) >= 0) { yandexAnalytics.onEvent(event.eventType, event); diff --git a/modules/yandexBidAdapter.js b/modules/yandexBidAdapter.js index e694dbcfe03..97bd793d633 100644 --- a/modules/yandexBidAdapter.js +++ b/modules/yandexBidAdapter.js @@ -2,7 +2,7 @@ import { registerBidder } from '../src/adapters/bidderFactory.js'; import { config } from '../src/config.js'; import { BANNER, NATIVE } from '../src/mediaTypes.js'; import { convertOrtbRequestToProprietaryNative } from '../src/native.js'; -import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel } from '../src/utils.js'; +import { _each, _map, deepAccess, deepSetValue, formatQS, triggerPixel, logInfo } from '../src/utils.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').Bid} Bid @@ -181,7 +181,7 @@ export const spec = { } const queryParamsString = formatQS(queryParams); - return { + const request = { method: 'POST', url: BIDDER_URL + `/${pageId}?${queryParamsString}`, data, @@ -190,6 +190,10 @@ export const spec = { }, bidRequest, }; + + logInfo('ServerRequest', request); + + return request; }); }, diff --git a/modules/yandexIdSystem.js b/modules/yandexIdSystem.js new file mode 100644 index 00000000000..e21642abf0b --- /dev/null +++ b/modules/yandexIdSystem.js @@ -0,0 +1,165 @@ +/** + * The {@link module:modules/userId} module is required + * @module modules/yandexIdSystem + * @requires module:modules/userId + */ + +// @ts-check + +/** + * @typedef {import('../modules/userId/index.js').Submodule} Submodule + * @typedef {import('../modules/userId/index.js').SubmoduleConfig} SubmoduleConfig + */ + +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { submodule } from '../src/hook.js'; +import { getStorageManager, STORAGE_TYPE_COOKIES } from '../src/storageManager.js'; +import { logError, logInfo } from '../src/utils.js'; + +// .com suffix is just a convention for naming the bidder eids +// See https://github.com/prebid/Prebid.js/pull/11196#discussion_r1591165139 +export const BIDDER_EID_KEY = 'yandex.com'; +export const YANDEX_ID_KEY = 'yandexId'; +export const YANDEX_EXT_COOKIE_NAMES = ['_ym_fa']; +export const BIDDER_CODE = 'yandex'; +export const YANDEX_USER_ID_KEY = '_ym_uid'; +export const YANDEX_STORAGE_TYPE = STORAGE_TYPE_COOKIES; +export const YANDEX_MIN_EXPIRE_DAYS = 30; + +export const PREBID_STORAGE = getStorageManager({ + moduleType: MODULE_TYPE_UID, + moduleName: BIDDER_CODE, + bidderCode: undefined +}); + +/** @type {Submodule} */ +export const yandexIdSubmodule = { + name: BIDDER_CODE, + /** + * Decodes the stored id value for passing to bid requests. + * @param {string} value + */ + decode(value) { + logInfo(`Decoded ${YANDEX_ID_KEY}`, value); + + return { [YANDEX_ID_KEY]: value }; + }, + getId(submoduleConfig, _consentData, storedId) { + if (checkConfigHasErrorsAndReport(submoduleConfig)) { + return; + } + + if (storedId) { + logInfo('Got storedId', storedId); + return { + id: storedId + }; + } + + return { + id: new YandexIdGenerator().generate(), + }; + }, + eids: { + [YANDEX_ID_KEY]: { + source: BIDDER_EID_KEY, + /** + * Agent Type 1 means that it is an ID + * which is tied to a specific web browser or device (cookie-based, probabilistic, or other). + * @see https://github.com/InteractiveAdvertisingBureau/AdCOM/blob/main/AdCOM%20v1.0%20FINAL.md#list--agent-types- + */ + atype: 1, + getUidExt() { + if (PREBID_STORAGE.cookiesAreEnabled()) { + return YANDEX_EXT_COOKIE_NAMES.reduce((acc, cookieName) => ({ + ...acc, + [cookieName]: PREBID_STORAGE.getCookie(cookieName), + }), {}); + } + }, + }, + }, +}; + +/** + * @param {SubmoduleConfig} submoduleConfig + * @returns {boolean} `true` - when there are errors, `false` - otherwise. + */ +function checkConfigHasErrorsAndReport(submoduleConfig) { + let error = false; + + const READABLE_MODULE_NAME = 'Yandex ID module'; + + if (submoduleConfig.storage == null) { + logError(`Misconfigured ${READABLE_MODULE_NAME}. "storage" is required.`); + return true; + } + + if (submoduleConfig.storage.name !== YANDEX_USER_ID_KEY) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.name" is expected to be "${YANDEX_USER_ID_KEY}", actual is "${submoduleConfig.storage.name}"`); + error = true; + } + + if (submoduleConfig.storage.type !== YANDEX_STORAGE_TYPE) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.type" is expected to be "${YANDEX_STORAGE_TYPE}", actual is "${submoduleConfig.storage.type}"`); + error = true; + } + + if ((submoduleConfig.storage.expires ?? 0) < YANDEX_MIN_EXPIRE_DAYS) { + logError(`Misconfigured ${READABLE_MODULE_NAME}, "storage.expires" is expected not to be less than "${YANDEX_MIN_EXPIRE_DAYS}", actual is "${submoduleConfig.storage.expires}"`); + error = true; + } + + return error; +} + +/** + * Yandex-specific generator for uid. Needs to be compatible with Yandex Metrica tag. + * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/uid/uid.ts#L51 + */ +class YandexIdGenerator { + generate() { + const yandexId = [ + this._getCurrentSecTimestamp(), + this._getRandomInteger(1000000, 999999999), + ].join(''); + + logInfo(`Generated ${YANDEX_ID_KEY}`, yandexId); + + return yandexId; + } + + _getCurrentSecTimestamp() { + return Math.round(Date.now() / 1000); + } + + /** + * @param {number} min + * @param {number} max + */ + _getRandomInteger(min, max) { + const generateRandom = this._getRandomGenerator(); + + /** + * Needs to be compatible with Yandex Metrica `getRandom` function. + * @see https://github.com/yandex/metrica-tag/blob/main/src/utils/number/random.ts#L12 + */ + return Math.floor(generateRandom() * (max - min)) + min; + } + + _getRandomGenerator() { + if (window.crypto) { + return () => { + const buffer = new Uint32Array(1); + crypto.getRandomValues(buffer); + + return buffer[0] / 0xffffffff; + }; + } + + // Polyfill for environments that don't support Crypto API + return () => Math.random(); + } +} + +submodule('userId', yandexIdSubmodule); diff --git a/modules/yieldlabBidAdapter.js b/modules/yieldlabBidAdapter.js index 23ff94f62a1..9be01096084 100644 --- a/modules/yieldlabBidAdapter.js +++ b/modules/yieldlabBidAdapter.js @@ -201,7 +201,7 @@ export const spec = { referrer: '', ad: ``, meta: { - advertiserDomains: (matchedBid.advertiser) ? matchedBid.advertiser : 'n/a', + advertiserDomains: [(matchedBid.advertiser) ? matchedBid.advertiser : 'n/a'], }, }; @@ -555,7 +555,7 @@ function getBidFloor(bid, sizes) { mediaType: mediaType !== undefined && spec.supportedMediaTypes.includes(mediaType) ? mediaType : '*', size: sizes.length !== 1 ? '*' : sizes[0].split(DIMENSION_SIGN), }); - if (floor.currency === CURRENCY_CODE) { + if (floor?.currency === CURRENCY_CODE) { return (floor.floor * 100).toFixed(0); } return undefined; diff --git a/modules/yieldmoBidAdapter.js b/modules/yieldmoBidAdapter.js index fe99adcec5f..af01ee73f09 100644 --- a/modules/yieldmoBidAdapter.js +++ b/modules/yieldmoBidAdapter.js @@ -177,7 +177,7 @@ export const spec = { serverRequest.topics = topicsData; } if (eids.length) { - serverRequest.user = { eids }; + deepSetValue(serverRequest, 'user.ext.eids', eids); }; serverRequests.push({ method: 'POST', @@ -257,7 +257,7 @@ function hasVideoMediaType(bidRequest) { * @param request bid request */ function addPlacement(request) { - const gpid = deepAccess(request, 'ortb2Imp.ext.data.pbadslot'); + const gpid = deepAccess(request, 'ortb2Imp.ext.gpid') || deepAccess(request, 'ortb2Imp.ext.data.pbadslot'); const placementInfo = { placement_id: request.adUnitCode, callback_id: request.bidId, @@ -337,8 +337,7 @@ function createNewVideoBid(response, bidRequest) { mediaType: VIDEO, }, }; - - if (imp.video.placement && imp.video.placement !== 1) { + if (imp.video.plcmt && imp.video.plcmt !== 1) { const renderer = Renderer.install({ url: OUTSTREAM_VIDEO_PLAYER_URL, config: { @@ -349,7 +348,7 @@ function createNewVideoBid(response, bidRequest) { allowVpaid: true, autoPlay: true, preload: true, - mute: true + mute: true, }, id: imp.tagid, loaded: false, @@ -471,7 +470,7 @@ function getTopics(bidderRequest) { * @return Object OpenRTB's 'imp' (impression) object */ function openRtbImpression(bidRequest) { - const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); + const gpid = deepAccess(bidRequest, 'ortb2Imp.ext.gpid') || deepAccess(bidRequest, 'ortb2Imp.ext.data.pbadslot'); const size = extractPlayerSize(bidRequest); const imp = { id: bidRequest.bidId, @@ -653,15 +652,6 @@ function validateVideoParams(bid) { validate('video.mimes', val => isDefined(val), paramRequired); validate('video.mimes', val => isArray(val) && val.every(v => isStr(v)), paramInvalid, 'array of strings, ex: ["video/mp4"]'); - - 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 => isNumber(val), paramInvalid, 'number, ex: 5'); - } - validate('video.protocols', val => isDefined(val), paramRequired); validate('video.api', val => isDefined(val), paramRequired); diff --git a/modules/yuktamediaAnalyticsAdapter.js b/modules/yuktamediaAnalyticsAdapter.js index 25e4dc73b74..19825041589 100644 --- a/modules/yuktamediaAnalyticsAdapter.js +++ b/modules/yuktamediaAnalyticsAdapter.js @@ -1,12 +1,11 @@ import {buildUrl, generateUUID, getWindowLocation, logError, logInfo, parseSizesInput, parseUrl} from '../src/utils.js'; -import {ajax} from '../src/ajax.js'; +import {ajax, fetch} from '../src/ajax.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; import adapterManager from '../src/adapterManager.js'; import { EVENTS, STATUS } from '../src/constants.js'; import {getStorageManager} from '../src/storageManager.js'; import {getRefererInfo} from '../src/refererDetection.js'; import {includes as strIncludes} from '../src/polyfill.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import {MODULE_TYPE_ANALYTICS} from '../src/activities/modules.js'; const MODULE_CODE = 'yuktamedia'; @@ -37,7 +36,7 @@ const _pageInfo = { referer: referer, refererDomain: parseUrl(referer).host, yuktamediaAnalyticsVersion: yuktamediaAnalyticsVersion, - prebidVersion: getGlobal().version + prebidVersion: 'v' + 'prebid.version$' }; function getParameterByName(param) { @@ -51,10 +50,6 @@ function getParameterByName(param) { return vars[param] ? vars[param] : ''; } -function isNavigatorSendBeaconSupported() { - return ('navigator' in window) && ('sendBeacon' in window.navigator); -} - function updateSessionId() { if (isSessionIdTimeoutExpired()) { let newSessionId = generateUUID(); @@ -89,11 +84,14 @@ function send(data, status) { hostname: 'analytics-prebid.yuktamedia.com', pathname: '/api/bids' }); - if (isNavigatorSendBeaconSupported()) { - window.navigator.sendBeacon(yuktamediaAnalyticsRequestUrl, JSON.stringify(data)); - } else { + fetch(yuktamediaAnalyticsRequestUrl, { + body: JSON.stringify(data), + keepalive: true, + withCredentials: true, + method: 'POST' + }).catch((_e) => { ajax(yuktamediaAnalyticsRequestUrl, undefined, JSON.stringify(data), { method: 'POST', contentType: 'text/plain' }); - } + }); } var yuktamediaAnalyticsAdapter = Object.assign(adapter({ analyticsType: 'endpoint' }), { diff --git a/modules/zeta_global_sspAnalyticsAdapter.js b/modules/zeta_global_sspAnalyticsAdapter.js index f0933d2f62f..805e2c51c81 100644 --- a/modules/zeta_global_sspAnalyticsAdapter.js +++ b/modules/zeta_global_sspAnalyticsAdapter.js @@ -1,7 +1,7 @@ -import {logInfo, logError} from '../src/utils.js'; -import { ajax } from '../src/ajax.js'; +import {logError} from '../src/utils.js'; +import {ajax} from '../src/ajax.js'; import adapterManager from '../src/adapterManager.js'; -import { EVENTS } from '../src/constants.js'; +import {EVENTS} from '../src/constants.js'; import adapter from '../libraries/analyticsAdapter/AnalyticsAdapter.js'; @@ -10,13 +10,9 @@ const ADAPTER_CODE = 'zeta_global_ssp'; const BASE_URL = 'https://ssp.disqus.com/prebid/event'; const LOG_PREFIX = 'ZetaGlobalSsp-Analytics: '; -const cache = { - auctions: {} -}; - /// /////////// VARIABLES //////////////////////////////////// -let publisherId; // int +let zetaParams; /// /////////// HELPER FUNCTIONS ///////////////////////////// @@ -28,154 +24,106 @@ function sendEvent(eventType, event) { ); } -function getZetaParams(event) { - if (event.adUnits) { - for (const i in event.adUnits) { - const unit = event.adUnits[i]; - if (unit.bids) { - for (const j in unit.bids) { - const bid = unit.bids[j]; - if (bid.bidder === ADAPTER_CODE && bid.params) { - return bid.params; - } - } - } - } - } - return null; -} - /// /////////// ADAPTER EVENT HANDLER FUNCTIONS ////////////// function adRenderSucceededHandler(args) { - let eventType = EVENTS.AD_RENDER_SUCCEEDED - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - adId: args.adId, + zetaParams: zetaParams, + domain: args.doc?.location?.host, + page: args.doc?.location?.host + args.doc?.location?.pathname, bid: { adId: args.bid?.adId, - auctionId: args.bid?.auctionId, - adUnitCode: args.bid?.adUnitCode, - bidId: args.bid?.bidId, requestId: args.bid?.requestId, - bidderCode: args.bid?.bidderCode, - mediaTypes: args.bid?.mediaTypes, - sizes: args.bid?.sizes, - adserverTargeting: args.bid?.adserverTargeting, - cpm: args.bid?.cpm, + auctionId: args.bid?.auctionId, creativeId: args.bid?.creativeId, + bidder: args.bid?.bidderCode, mediaType: args.bid?.mediaType, - renderer: args.bid?.renderer, size: args.bid?.size, + adomain: args.bid?.adserverTargeting?.hb_adomain, timeToRespond: args.bid?.timeToRespond, - params: args.bid?.params + cpm: args.bid?.cpm, + adUnitCode: args.bid?.adUnitCode }, - doc: { - location: args.doc?.location + device: { + ua: navigator.userAgent } } - - // set zetaParams from cache - if (event.bid && event.bid.auctionId) { - const zetaParams = cache.auctions[event.bid.auctionId]; - if (zetaParams) { - event.bid.params = [ zetaParams ]; - } - } - - sendEvent(eventType, event); + sendEvent(EVENTS.AD_RENDER_SUCCEEDED, event); } function auctionEndHandler(args) { - let eventType = EVENTS.AUCTION_END; - logInfo(LOG_PREFIX + 'handle ' + eventType + ' event'); - const event = { - auctionId: args.auctionId, - adUnits: args.adUnits, + zetaParams: zetaParams, bidderRequests: args.bidderRequests?.map(br => ({ bidderCode: br?.bidderCode, - refererInfo: br?.refererInfo, + domain: br?.refererInfo?.domain, + page: br?.refererInfo?.page, bids: br?.bids?.map(b => ({ - adUnitCode: b?.adUnitCode, - auctionId: b?.auctionId, bidId: b?.bidId, - requestId: b?.requestId, - bidderCode: b?.bidderCode, - mediaTypes: b?.mediaTypes, - sizes: b?.sizes, + auctionId: b?.auctionId, bidder: b?.bidder, - params: b?.params + mediaType: b?.mediaTypes?.video ? 'VIDEO' : (b?.mediaTypes?.banner ? 'BANNER' : undefined), + size: b?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + device: b?.ortb2?.device, + adUnitCode: b?.adUnitCode })) })), bidsReceived: args.bidsReceived?.map(br => ({ adId: br?.adId, - adserverTargeting: { - hb_adomain: br?.adserverTargeting?.hb_adomain - }, - cpm: br?.cpm, + requestId: br?.requestId, creativeId: br?.creativeId, + bidder: br?.bidder, mediaType: br?.mediaType, - renderer: br?.renderer, size: br?.size, + adomain: br?.adserverTargeting?.hb_adomain, timeToRespond: br?.timeToRespond, - adUnitCode: br?.adUnitCode, - auctionId: br?.auctionId, - bidId: br?.bidId, - requestId: br?.requestId, - bidderCode: br?.bidderCode, - mediaTypes: br?.mediaTypes, - sizes: br?.sizes, - bidder: br?.bidder, - params: br?.params + cpm: br?.cpm, + adUnitCode: br?.adUnitCode })) } + sendEvent(EVENTS.AUCTION_END, event); +} - // save zetaParams to cache - const zetaParams = getZetaParams(event); - if (zetaParams && event.auctionId) { - cache.auctions[event.auctionId] = zetaParams; +function bidTimeoutHandler(args) { + const event = { + zetaParams: zetaParams, + domain: args.find(t => t?.ortb2?.site?.domain)?.ortb2?.site?.domain, + page: args.find(t => t?.ortb2?.site?.page)?.ortb2?.site?.page, + timeouts: args.map(t => ({ + bidId: t?.bidId, + auctionId: t?.auctionId, + bidder: t?.bidder, + mediaType: t?.mediaTypes?.video ? 'VIDEO' : (t?.mediaTypes?.banner ? 'BANNER' : undefined), + size: t?.sizes?.filter(s => s && s.length === 2).filter(s => Number.isInteger(s[0]) && Number.isInteger(s[1])).map(s => s[0] + 'x' + s[1]).find(s => s), + timeout: t?.timeout, + device: t?.ortb2?.device, + adUnitCode: t?.adUnitCode + })) } - - sendEvent(eventType, event); + sendEvent(EVENTS.BID_TIMEOUT, event); } /// /////////// ADAPTER DEFINITION /////////////////////////// -let baseAdapter = adapter({ analyticsType: 'endpoint' }); +let baseAdapter = adapter({analyticsType: 'endpoint'}); let zetaAdapter = Object.assign({}, baseAdapter, { enableAnalytics(config = {}) { - let error = false; - - if (typeof config.options === 'object') { - if (config.options.sid) { - publisherId = Number(config.options.sid); - } + if (config.options && config.options.sid) { + zetaParams = config.options; + baseAdapter.enableAnalytics.call(this, config); } else { logError(LOG_PREFIX + 'Config not found'); - error = true; - } - - if (!publisherId) { - logError(LOG_PREFIX + 'Missing sid (publisher id)'); - error = true; - } - - if (error) { logError(LOG_PREFIX + 'Analytics is disabled due to error(s)'); - } else { - baseAdapter.enableAnalytics.call(this, config); } }, disableAnalytics() { - publisherId = undefined; + zetaParams = undefined; baseAdapter.disableAnalytics.apply(this, arguments); }, - track({ eventType, args }) { + track({eventType, args}) { switch (eventType) { case EVENTS.AD_RENDER_SUCCEEDED: adRenderSucceededHandler(args); @@ -183,6 +131,9 @@ let zetaAdapter = Object.assign({}, baseAdapter, { case EVENTS.AUCTION_END: auctionEndHandler(args); break; + case EVENTS.BID_TIMEOUT: + bidTimeoutHandler(args); + break; } } }); diff --git a/modules/zeta_global_sspBidAdapter.js b/modules/zeta_global_sspBidAdapter.js index f3e2e12c143..62e60db020f 100644 --- a/modules/zeta_global_sspBidAdapter.js +++ b/modules/zeta_global_sspBidAdapter.js @@ -3,7 +3,6 @@ import {registerBidder} from '../src/adapters/bidderFactory.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; import {parseDomain} from '../src/refererDetection.js'; -import {ajax} from '../src/ajax.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -15,7 +14,6 @@ import {ajax} from '../src/ajax.js'; const BIDDER_CODE = 'zeta_global_ssp'; const ENDPOINT_URL = 'https://ssp.disqus.com/bid/prebid'; -const TIMEOUT_URL = 'https://ssp.disqus.com/timeout/prebid'; 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'; @@ -130,8 +128,8 @@ export const spec = { id: bidderRequest.bidderRequestId, cur: [DEFAULT_CUR], imp: imps, - site: params.site ? params.site : {}, - device: {...(bidderRequest.ortb2?.device || {}), ...params.device}, + site: {...bidderRequest?.ortb2?.site, ...params?.site}, + device: {...bidderRequest?.ortb2?.device, ...params?.device}, user: params.user ? params.user : {}, app: params.app ? params.app : {}, ext: { @@ -149,6 +147,18 @@ export const spec = { payload.device.w = screen.width; payload.device.h = screen.height; + if (bidderRequest.ortb2?.user?.geo && bidderRequest.ortb2?.device?.geo) { + payload.device.geo = { ...payload.device.geo, ...bidderRequest.ortb2?.device.geo }; + payload.user.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } else { + if (bidderRequest.ortb2?.user?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.user.geo }; + } + if (bidderRequest.ortb2?.device?.geo) { + payload.user.geo = payload.device.geo = { ...payload.user.geo, ...bidderRequest.ortb2?.device.geo }; + } + } + if (bidderRequest?.ortb2?.device?.sua) { payload.device.sua = bidderRequest.ortb2.device.sua; } @@ -181,6 +191,14 @@ export const spec = { payload.tmax = bidderRequest.timeout; } + if (bidderRequest?.ortb2?.bcat) { + payload.bcat = bidderRequest.ortb2.bcat; + } + + if (bidderRequest?.ortb2?.badv) { + payload.badv = bidderRequest.ortb2.badv; + } + provideEids(validBidRequests[0], payload); provideSegments(bidderRequest, payload); const url = params.sid ? ENDPOINT_URL.concat('?sid=', params.sid) : ENDPOINT_URL; @@ -268,25 +286,6 @@ export const spec = { url: USER_SYNC_URL_IMAGE + syncurl }]; } - }, - - onTimeout: function(timeoutData) { - if (timeoutData) { - const payload = timeoutData.map(d => ({ - bidder: d?.bidder, - shortname: d?.params?.map(p => p?.tags?.shortname).find(p => p), - sid: d?.params?.map(p => p?.sid).find(p => p), - country: d?.ortb2?.device?.geo?.country, - devicetype: d?.ortb2?.device?.devicetype - })); - ajax(TIMEOUT_URL, null, JSON.stringify(payload), { - method: 'POST', - options: { - withCredentials: false, - contentType: 'application/json' - } - }); - } } } diff --git a/package-lock.json b/package-lock.json index bd93f27388e..25f60db14dc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,44 +1,43 @@ { "name": "prebid.js", - "version": "8.49.8", + "version": "9.22.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.49.8", + "version": "9.22.0", "license": "Apache-2.0", "dependencies": { - "@babel/core": "^7.16.7", + "@babel/core": "^7.25.2", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^4.2.0", "dlv": "1.1.3", - "dset": "3.1.2", + "dset": "3.1.4", "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^7.1.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@wdio/browserstack-service": "^8.29.0", - "@wdio/cli": "^8.29.0", + "@babel/register": "^7.24.6", + "@wdio/browserstack-service": "^9.0.5", + "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", - "@wdio/local-runner": "^8.29.0", + "@wdio/local-runner": "^9.0.5", "@wdio/mocha-framework": "^8.29.0", "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", "babel-plugin-istanbul": "^6.1.1", - "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -47,8 +46,8 @@ "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jsdoc": "^38.1.6", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", "eslint-plugin-promise": "^5.1.0", @@ -56,7 +55,7 @@ "execa": "^1.0.0", "faker": "^5.5.3", "fs.extra": "^1.3.2", - "gulp": "^4.0.0", + "gulp": "^4.0.2", "gulp-clean": "^0.4.0", "gulp-concat": "^2.6.0", "gulp-connect": "^5.7.0", @@ -91,20 +90,20 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", - "mocha": "^10.0.0", + "mocha": "^10.7.3", "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", "resolve-from": "^5.0.0", - "sinon": "^4.1.3", + "sinon": "^4.5.0", "through2": "^4.0.2", "url": "^0.11.0", "url-parse": "^1.0.5", "video.js": "^7.17.0", "videojs-contrib-ads": "^6.9.0", - "videojs-ima": "^1.11.0", + "videojs-ima": "^2.3.0", "videojs-playlist": "^5.0.0", - "webdriverio": "^7.6.1", + "webdriverio": "^9.0.9", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.0", @@ -112,7 +111,7 @@ "yargs": "^1.3.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "optionalDependencies": { "fsevents": "^2.3.2" @@ -131,11 +130,12 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" }, "engines": { @@ -143,28 +143,28 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "dependencies": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -198,14 +198,15 @@ } }, "node_modules/@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "dependencies": { - "@babel/types": "^7.24.5", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" }, "engines": { "node": ">=6.9.0" @@ -234,13 +235,13 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -310,23 +311,23 @@ } }, "node_modules/@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "dependencies": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -344,26 +345,25 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" }, "engines": { "node": ">=6.9.0" @@ -439,7 +439,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -457,25 +457,25 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==", "engines": { "node": ">=6.9.0" } @@ -494,13 +494,12 @@ } }, "node_modules/@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" }, "engines": { "node": ">=6.9.0" @@ -510,6 +509,7 @@ "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dev": true, "dependencies": { "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", @@ -521,9 +521,12 @@ } }, "node_modules/@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "dependencies": { + "@babel/types": "^7.26.3" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -1685,6 +1688,128 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/register": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "dev": true, + "dependencies": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/register/node_modules/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==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "dependencies": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/register/node_modules/pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/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==", + "dev": true, + "dependencies": { + "find-up": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@babel/register/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -1702,31 +1827,28 @@ } }, "node_modules/@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "dependencies": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", + "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", + "dependencies": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1735,18 +1857,40 @@ } }, "node_modules/@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@browserstack/ai-sdk-node": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.9.tgz", + "integrity": "sha512-RGLWcxadVEkafFaoeAPnIGxS/C+DtHDYWZ3Sy+P5VvWlDWhN9hZU5lZGMsUZg+XmrnuhodEEaZnJQV9P0IEeMQ==", + "dev": true, + "dependencies": { + "axios": "^1.7.4", + "uuid": "9.0.1" + } + }, + "node_modules/@browserstack/ai-sdk-node/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1766,17 +1910,404 @@ } }, "node_modules/@es-joy/jsdoccomment": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", - "integrity": "sha512-pM6WQKcuAtdYoqCsXSvVSu3Ij8K0HY50L8tIheOKHDl0wH1uA4zbP88etY8SIeP16NVCMCTFU+Q2DahSKheGGQ==", + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", + "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", "dev": true, "dependencies": { - "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~2.2.5" + "@types/eslint": "^8.56.5", + "@types/estree": "^1.0.5", + "@typescript-eslint/types": "^7.2.0", + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" }, "engines": { - "node": "^12 || ^14 || ^16 || ^17" + "node": ">=16" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" } }, "node_modules/@eslint/eslintrc": { @@ -1990,6 +2521,7 @@ "version": "0.5.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz", "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==", + "deprecated": "Use @eslint/config-array instead", "dev": true, "dependencies": { "@humanwhocodes/object-schema": "^1.2.0", @@ -2004,8 +2536,255 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core/node_modules/@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "dependencies": { + "undici-types": "~6.20.0" + } + }, + "node_modules/@inquirer/core/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@inquirer/core/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/@inquirer/core/node_modules/undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + }, + "node_modules/@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "dependencies": { + "mute-stream": "^1.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -2251,9 +3030,9 @@ } }, "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.25", @@ -2264,18 +3043,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -2336,6 +3103,18 @@ "node": ">=14" } }, + "node_modules/@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, "node_modules/@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", @@ -2384,6 +3163,23 @@ "node": ">=16.3.0" } }, + "node_modules/@puppeteer/browsers/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, "node_modules/@puppeteer/browsers/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -2402,22 +3198,34 @@ "node": ">=12" } }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "node_modules/@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "node_modules/@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true, "engines": { - "node": ">=14.16" + "node": ">=18" }, "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@sinonjs/commons": { @@ -2450,9 +3258,9 @@ } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "node_modules/@socket.io/component-emitter": { @@ -2461,53 +3269,12 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, - "node_modules/@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.1" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">= 10" - } - }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, - "node_modules/@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true - }, - "node_modules/@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "node_modules/@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -2576,16 +3343,6 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "node_modules/@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "dependencies": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "node_modules/@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -2595,12 +3352,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2637,15 +3388,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -2655,12 +3397,6 @@ "@types/unist": "^2" } }, - "node_modules/@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "node_modules/@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -2673,6 +3409,15 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, + "node_modules/@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/node": { "version": "20.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", @@ -2694,14 +3439,11 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, - "node_modules/@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "dependencies": { - "@types/node": "*" - } + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true }, "node_modules/@types/stack-utils": { "version": "2.0.3", @@ -2721,12 +3463,6 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, - "node_modules/@types/ua-parser-js": { - "version": "0.7.39", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", - "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", - "dev": true - }, "node_modules/@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -2749,19 +3485,25 @@ "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, + "node_modules/@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "node_modules/@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", "dev": true, "dependencies": { "@types/node": "*" } }, "node_modules/@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "dependencies": { "@types/yargs-parser": "*" @@ -2783,6 +3525,19 @@ "@types/node": "*" } }, + "node_modules/@typescript-eslint/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "dev": true, + "engines": { + "node": "^18.18.0 || >=20.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@videojs/http-streaming": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.2.tgz", @@ -2832,15 +3587,27 @@ "is-function": "^1.0.1" } }, + "node_modules/@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "dependencies": { + "tinyrainbow": "^1.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "dependencies": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" }, "funding": { "url": "https://opencollective.com/vitest" @@ -2908,159 +3675,77 @@ "optional": true }, "node_modules/@wdio/browserstack-service": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.36.1.tgz", - "integrity": "sha512-3xCQaKk7FUjTzDPYyQlUrLpfTlHz+WlSx9ppUG5ZNaJG08Gqu0nWyUCcJCdQXVpKeYTduX7DzIBIb7Efp/3+4g==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.4.1.tgz", + "integrity": "sha512-gRYh7xkQqE9/080S3R5Dz2zw4kQQzMgRO8lyf15R91TVJQkwhrXG6qeo1c3Ri1n7l34C6sJTlOf+ulQJXMUk9Q==", "dev": true, "dependencies": { + "@browserstack/ai-sdk-node": "1.5.9", "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.28.0", - "@wdio/reporter": "8.36.1", - "@wdio/types": "8.36.1", + "@wdio/logger": "9.1.3", + "@wdio/reporter": "9.2.14", + "@wdio/types": "9.2.2", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", - "got": "^12.6.1", - "uuid": "^9.0.0", - "webdriverio": "8.36.1", + "uuid": "^10.0.0", + "webdriverio": "9.4.1", "winston-transport": "^4.5.0", "yauzl": "^3.0.0" }, "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=16.3.0" + "node": ">=18.20.0" }, "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { + "version": "9.2.14", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.2.14.tgz", + "integrity": "sha512-njOqa9+w5zc9AY4fsUmW46EhZ2nzMFRXZPCmwnGZuYk81L3slVfAFhKk1wSTRhSbghjYAZndt9Yo3c4wFLh6Lg==", "dev": true, "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" + "@types/node": "^20.1.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" }, "engines": { - "node": ">= 14" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "node_modules/@wdio/browserstack-service/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "engines": { - "node": ">=8.0.0" + "node": ">=18.20.0" } }, "node_modules/@wdio/browserstack-service/node_modules/chalk": { @@ -3075,301 +3760,221 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "node_modules/@wdio/browserstack-service/node_modules/diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" + "node": ">=0.3.1" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "node_modules/@wdio/cli": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.4.1.tgz", + "integrity": "sha512-GDyAer63WDsr2ckXmrpUyAcIZFd3pCRIpi85rL1ZjnWthRy/UtwY0EHPMDuSeUEJ28iYwW3esKgq2ZKlsdbMeA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", + "@types/node": "^20.1.1", + "@vitest/snapshot": "^2.1.1", + "@wdio/config": "9.2.8", + "@wdio/globals": "9.4.1", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "async-exit-hook": "^2.0.1", + "chalk": "^5.2.0", + "chokidar": "^4.0.0", + "cli-spinners": "^3.0.0", + "dotenv": "^16.3.1", + "ejs": "^3.1.9", + "execa": "^9.2.0", "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" + "inquirer": "^11.0.1", + "lodash.flattendeep": "^4.4.0", + "lodash.pickby": "^4.6.0", + "lodash.union": "^4.6.0", + "read-pkg-up": "^10.0.0", + "recursive-readdir": "^2.2.3", + "tsx": "^4.7.2", + "webdriverio": "9.4.1", + "yargs": "^17.7.2" + }, + "bin": { + "wdio": "bin/wdio.js" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, "bin": { "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@wdio/cli/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "node_modules/@wdio/cli/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "mitt": "3.0.0" + "@types/node": "^20.1.0" }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "engines": { + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/cli/node_modules/@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">= 6" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/@wdio/cli/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "debug": "^4.3.4" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "node_modules/@wdio/cli/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/@wdio/cli/node_modules/chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 6" + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/@wdio/cli/node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/@wdio/cli/node_modules/execa": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", + "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" }, "engines": { - "node": ">=6" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@wdio/cli/node_modules/get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, - "optional": true, - "peer": true, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/@wdio/cli/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { "agent-base": "^7.0.2", @@ -3379,42 +3984,19 @@ "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@wdio/cli/node_modules/is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", "dev": true, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/lru-cache": { + "node_modules/@wdio/cli/node_modules/lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", @@ -3423,358 +4005,268 @@ "node": ">=12" } }, - "node_modules/@wdio/browserstack-service/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/@wdio/cli/node_modules/npm-run-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": ">=18" }, "funding": { - "url": "https://github.com/sponsors/isaacs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/@wdio/cli/node_modules/parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", "dev": true, - "optional": true, - "peer": true + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@wdio/cli/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@wdio/browserstack-service/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/@wdio/cli/node_modules/pretty-ms": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", + "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "parse-ms": "^4.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=18" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/browserstack-service/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "node_modules/@wdio/cli/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "node_modules/@wdio/cli/node_modules/readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", "dev": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": ">= 14.16.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@wdio/browserstack-service/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - }, - "node_modules/@wdio/browserstack-service/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "node_modules/@wdio/cli/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=10" } }, - "node_modules/@wdio/browserstack-service/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "node_modules/@wdio/cli/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true, - "dependencies": { - "type-fest": "^2.12.2" - }, "engines": { - "node": ">=14.16" + "node": ">=14" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/@wdio/browserstack-service/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/@wdio/cli/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/@wdio/browserstack-service/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "engines": { - "node": ">=12.20" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "node_modules/@wdio/cli/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "optional": true, - "peer": true, + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, "engines": { - "node": "*" + "node": ">=12" } }, - "node_modules/@wdio/browserstack-service/node_modules/webdriverio": { + "node_modules/@wdio/concise-reporter": { "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.36.1.tgz", + "integrity": "sha512-eeGD7x/fZ+aK2zWBmEUri1IrjfGzHomUhpL6r1VB/IKn6Ow9f94K8Xs+H/Qd9OHqcvR7Oqy8CbfH132TE/04+g==", "dev": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", + "@wdio/reporter": "8.36.1", "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" }, "engines": { "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } } }, - "node_modules/@wdio/browserstack-service/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { - "node": ">=10.0.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/browserstack-service/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@wdio/config": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.2.8.tgz", + "integrity": "sha512-EGMmBPGJbz6RmgMjebRWkWu3fGyeTIRcusF4UA4f2tiUEKY8nbzUO/ZyDjVQNR+YVB40q0jcqAqpszYRrIzzeg==", "dev": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=12" + "node": ">=18.20.0" } }, - "node_modules/@wdio/browserstack-service/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "node_modules/@wdio/config/node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "node_modules/@wdio/cli": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.36.1.tgz", - "integrity": "sha512-LZBZiwcvvv5P0HuRXt8IV09UiFT5dnDr1Ag5u2roJL2D7l8wDHHa70PXw9MmlbrnyFCUN3hO7FQVUi9MAsDbDQ==", + "node_modules/@wdio/config/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "dependencies": { - "@types/node": "^20.1.1", - "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.36.1", - "@wdio/globals": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "async-exit-hook": "^2.0.1", - "chalk": "^5.2.0", - "chokidar": "^3.5.3", - "cli-spinners": "^2.9.0", - "dotenv": "^16.3.1", - "ejs": "^3.1.9", - "execa": "^8.0.1", - "import-meta-resolve": "^4.0.0", - "inquirer": "9.2.12", - "lodash.flattendeep": "^4.4.0", - "lodash.pickby": "^4.6.0", - "lodash.union": "^4.6.0", - "read-pkg-up": "10.0.0", - "recursive-readdir": "^2.2.3", - "webdriverio": "8.36.1", - "yargs": "^17.7.2" - }, - "bin": { - "wdio": "bin/wdio.js" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/@wdio/config/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/@puppeteer/browsers/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@wdio/config/node_modules/@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", "dev": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=12" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/agent-base": { + "node_modules/@wdio/config/node_modules/agent-base": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", @@ -3786,107 +4278,149 @@ "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "node_modules/@wdio/config/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/config/node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", + "dev": true, "engines": { - "node": ">= 14" + "node": ">=16.0.0" } }, - "node_modules/@wdio/cli/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "node_modules/@wdio/config/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/archiver-utils/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@wdio/config/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "engines": { - "node": ">=8" + "node": ">=12" + } + }, + "node_modules/@wdio/config/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", + "dev": true, + "dependencies": { + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true + "node_modules/@wdio/config/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "node_modules/@wdio/cli/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@wdio/config/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/@wdio/cli/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "node_modules/@wdio/config/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "node_modules/@wdio/globals": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.4.1.tgz", + "integrity": "sha512-CTVAVJ7iFyT54XF9iRmNvsDB+WSHoztJPG9XPL/mHzQ2LYfSyUR8E/j+3iHbTx3v/qRNucgPcGwhxiuY2RcaDg==", "dev": true, "engines": { - "node": ">=8.0.0" + "node": ">=18.20.0" + }, + "optionalDependencies": { + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.4.1" } }, - "node_modules/@wdio/cli/node_modules/chalk": { + "node_modules/@wdio/globals/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", + "dev": true, + "optional": true, + "peer": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/globals/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": "^12.17.0 || ^14.13 || >=16.0.0" }, @@ -3894,722 +4428,454 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", + "node_modules/@wdio/globals/node_modules/expect-webdriverio": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.5.tgz", + "integrity": "sha512-h04OGd7ZksVj8bgv3bYdjFpmJuKeCnyRrBmpMxYpMDmYSspxg9vsSr0kD5p9oOM16bX0ZXEVXr42RbI2hoLpTw==", "dev": true, "optional": true, - "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "engines": { + "node": ">=18 || >=20 || >=22" + }, + "peerDependencies": { + "@wdio/globals": "^9.0.0", + "@wdio/logger": "^9.0.0", + "webdriverio": "^9.0.0" + }, + "peerDependenciesMeta": { + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false + } + } + }, + "node_modules/@wdio/local-runner": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.4.1.tgz", + "integrity": "sha512-MM5VM0V7zvajICr6eNROjkppRhGNpdV4nU5hrgSap92nou8G+zBgLxJ45P5BzLw67KQTOEa1E32b/zCBEkO+0g==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.1.3", + "@wdio/repl": "9.0.8", + "@wdio/runner": "9.4.1", + "@wdio/types": "9.2.2", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" }, "engines": { - "node": ">=12.13.0" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/compress-commons/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/@wdio/local-runner/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, + "dependencies": { + "@types/node": "^20.1.0" + }, "engines": { - "node": ">=8" + "node": ">=18.20.0" + } + }, + "node_modules/@wdio/local-runner/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "node_modules/@wdio/logger": { + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", "dev": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 14" + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/cli/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "node_modules/@wdio/logger/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/devtools": { + "node_modules/@wdio/mocha-framework": { "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.36.1.tgz", + "integrity": "sha512-G0h5AeneMNtoh9CcVQ82OCKj0axxUOEotEcInDu8V6UJbUywNJVL/bdTMKdaq5i84Hnc+s1LUKmLvN95F+lHGA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { + "@types/mocha": "^10.0.0", "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", "@wdio/types": "8.36.1", "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" + "mocha": "^10.0.0" }, "engines": { "node": "^16.13 || >=18" } }, - "node_modules/@wdio/cli/node_modules/devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "node_modules/@wdio/mocha-framework/node_modules/@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": "^16.13 || >=18" + } + }, + "node_modules/@wdio/mocha-framework/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/@wdio/protocols": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.2.2.tgz", + "integrity": "sha512-0GMUSHCbYm+J+rnRU6XPtaUgVCRICsiH6W5zCXpePm3wLlbmg/mvZ+4OnNErssbpIOulZuAmC2jNmut2AEfWSw==", + "dev": true + }, + "node_modules/@wdio/repl": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", + "integrity": "sha512-3iubjl4JX5zD21aFxZwQghqC3lgu+mSs8c3NaiYYNCC+IT5cI/8QuKlgh9s59bu+N3gG988jqMJeCYlKuUv/iw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4" + "@types/node": "^20.1.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "node_modules/@wdio/reporter": { + "version": "8.36.1", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.36.1.tgz", + "integrity": "sha512-HcXr9XKq/6kPC9nexMRXIc/ft3Lvp0yCaW5tps01Axus9wbi5ysLHi2z5sB84F2YdpM+aRf7Lac56xkc4Jldeg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "mitt": "3.0.0" + "@types/node": "^20.1.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.36.1", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" }, - "peerDependencies": { - "devtools-protocol": "*" + "engines": { + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/@wdio/reporter/node_modules/@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/@wdio/reporter/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/@wdio/runner": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.4.1.tgz", + "integrity": "sha512-WUpYamLafv+GUofGqvA2HO1kSVnwLyPO5ujq3TS/YaJLnUDOj4bjHiRo5+KDbKw1xhqjW0GjbfDupVU5LeL9iw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "@types/node": "^20.11.28", + "@wdio/config": "9.2.8", + "@wdio/globals": "9.4.1", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", + "webdriver": "9.4.1", + "webdriverio": "9.4.1" }, "engines": { - "node": ">= 6" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.0.0" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "bin": { + "browsers": "lib/cjs/main-cli.js" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=18" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/@wdio/runner/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">= 6" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/@wdio/runner/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/@wdio/runner/node_modules/@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=6" + "node": ">=18.20.0" } }, - "node_modules/@wdio/cli/node_modules/devtools/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/@wdio/runner/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=12" + "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/@wdio/runner/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/cli/node_modules/execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "node_modules/@wdio/runner/node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true, - "dependencies": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" - }, "engines": { - "node": ">=16.17" - }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" - } - }, - "node_modules/@wdio/cli/node_modules/get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true, - "engines": { - "node": ">=16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/cli/node_modules/is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/@wdio/cli/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/@wdio/cli/node_modules/mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", - "dev": true, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, - "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=16.0.0" } }, - "node_modules/@wdio/cli/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@wdio/cli/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/@wdio/runner/node_modules/expect-webdriverio": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.5.tgz", + "integrity": "sha512-h04OGd7ZksVj8bgv3bYdjFpmJuKeCnyRrBmpMxYpMDmYSspxg9vsSr0kD5p9oOM16bX0ZXEVXr42RbI2hoLpTw==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" }, "engines": { - "node": "4.x || >=6.0.0" + "node": ">=18 || >=20 || >=22" }, "peerDependencies": { - "encoding": "^0.1.0" + "@wdio/globals": "^9.0.0", + "@wdio/logger": "^9.0.0", + "webdriverio": "^9.0.0" }, "peerDependenciesMeta": { - "encoding": { - "optional": true + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false } } }, - "node_modules/@wdio/cli/node_modules/npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", - "dev": true, - "dependencies": { - "path-key": "^4.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/onetime": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "node_modules/@wdio/runner/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "mimic-fn": "^4.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/path-key": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", - "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "node_modules/@wdio/runner/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "engines": { "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "node_modules/@wdio/runner/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "socks-proxy-agent": "^8.0.2" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - }, - "node_modules/@wdio/cli/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" - } - }, - "node_modules/@wdio/cli/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/signal-exit": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", - "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", - "dev": true, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/@wdio/cli/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/@wdio/cli/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/@wdio/runner/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "engines": { - "node": ">=12.20" + "bin": { + "semver": "bin/semver.js" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/cli/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "optional": true, - "peer": true, "engines": { - "node": "*" + "node": ">=10" } }, - "node_modules/@wdio/cli/node_modules/webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "node_modules/@wdio/runner/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" - }, - "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } - } - }, - "node_modules/@wdio/cli/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/@wdio/cli/node_modules/yargs": { + "node_modules/@wdio/runner/node_modules/yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", @@ -4627,36 +4893,23 @@ "node": ">=12" } }, - "node_modules/@wdio/cli/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/concise-reporter": { + "node_modules/@wdio/spec-reporter": { "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.36.1.tgz", - "integrity": "sha512-eeGD7x/fZ+aK2zWBmEUri1IrjfGzHomUhpL6r1VB/IKn6Ow9f94K8Xs+H/Qd9OHqcvR7Oqy8CbfH132TE/04+g==", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.36.1.tgz", + "integrity": "sha512-VgAd8VQCfwKYz4A3BPDUYNIQxXhRSTaVNbmDzSlYfo5Jekygk7fz0LRFYBpJ69l7eQH0P5nzEyF92oW/rvE3VA==", "dev": true, "dependencies": { "@wdio/reporter": "8.36.1", "@wdio/types": "8.36.1", - "chalk": "^5.0.1", - "pretty-ms": "^7.0.1" + "chalk": "^5.1.2", + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" }, "engines": { "node": "^16.13 || >=18" } }, - "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "node_modules/@wdio/spec-reporter/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", @@ -4668,629 +4921,557 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/config": { + "node_modules/@wdio/types": { "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.36.1.tgz", - "integrity": "sha512-yCENnym0CrYuLKMJ3fv00WkjCR8QpPqVohGBkq5FvZOZpVJEpoG86Q8l4HtyRnd6ggMTKCA1vTQ/myhbPmZmaQ==", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.36.1.tgz", + "integrity": "sha512-kKtyJbypasKo/VQuJ6dTQQwFtHE9qoygjoCZjrQCLGraRSjOEiqZHPR0497wbeCvcgHIYyImbmcylqZNGUE0CQ==", "dev": true, "dependencies": { - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "decamelize": "^6.0.0", - "deepmerge-ts": "^5.0.0", - "glob": "^10.2.2", - "import-meta-resolve": "^4.0.0" + "@types/node": "^20.1.0" }, "engines": { "node": "^16.13 || >=18" } }, - "node_modules/@wdio/globals": { + "node_modules/@wdio/utils": { "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.36.1.tgz", - "integrity": "sha512-Qpj6gZCRNxqdVkTwYyi4JdeYO4tLSUj3Ti6yxO0v9A4IRaKW1tS29KUcGgjL9CFSBKAOi2zRY8vvFz1u6ewxtQ==", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.36.1.tgz", + "integrity": "sha512-xmgPHU11/o9n2FeRmDFkPRC0okiwA1i2xOcR2c3aSpuk99XkAm9RaMn/6u9LFaqsCpgaVxazcYEGSceO7U4hZA==", "dev": true, + "dependencies": { + "@puppeteer/browsers": "^1.6.0", + "@wdio/logger": "8.28.0", + "@wdio/types": "8.36.1", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.1.0", + "edgedriver": "^5.3.5", + "geckodriver": "^4.3.1", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.1.0", + "safaridriver": "^0.1.0", + "split2": "^4.2.0", + "wait-port": "^1.0.4" + }, "engines": { "node": "^16.13 || >=18" - }, - "optionalDependencies": { - "expect-webdriverio": "^4.11.2", - "webdriverio": "8.36.1" } }, - "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/@wdio/utils/node_modules/@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", "dev": true, - "optional": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": "^16.13 || >=18" } }, - "node_modules/@wdio/globals/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/@wdio/utils/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "optional": true, - "dependencies": { - "debug": "^4.3.4" - }, "engines": { - "node": ">= 14" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/globals/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "node_modules/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "dev": true, - "optional": true, "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, - "engines": { - "node": ">= 14" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "node_modules/@wdio/globals/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", + "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", + "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", + "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", + "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", "dev": true, - "optional": true, "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "node_modules/@wdio/globals/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", + "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", + "dev": true }, - "node_modules/@wdio/globals/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", + "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", "dev": true, - "optional": true, "dependencies": { - "balanced-match": "^1.0.0" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/wasm-gen": "1.12.1" } }, - "node_modules/@wdio/globals/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", + "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@xtuc/ieee754": "^1.2.0" } }, - "node_modules/@wdio/globals/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", + "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", "dev": true, - "optional": true, - "engines": { - "node": ">=8.0.0" + "dependencies": { + "@xtuc/long": "4.2.2" } }, - "node_modules/@wdio/globals/node_modules/chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.6", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", + "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", + "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/helper-wasm-section": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-opt": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1", + "@webassemblyjs/wast-printer": "1.12.1" } }, - "node_modules/@wdio/globals/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", + "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", "dev": true, - "optional": true, "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/@wdio/globals/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", + "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", "dev": true, - "optional": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-buffer": "1.12.1", + "@webassemblyjs/wasm-gen": "1.12.1", + "@webassemblyjs/wasm-parser": "1.12.1" } }, - "node_modules/@wdio/globals/node_modules/cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", + "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", "dev": true, - "optional": true, "dependencies": { - "node-fetch": "^2.6.12" + "@webassemblyjs/ast": "1.12.1", + "@webassemblyjs/helper-api-error": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6", + "@webassemblyjs/ieee754": "1.11.6", + "@webassemblyjs/leb128": "1.11.6", + "@webassemblyjs/utf8": "1.11.6" } }, - "node_modules/@wdio/globals/node_modules/devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", + "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, + "@webassemblyjs/ast": "1.12.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "dev": true, "engines": { - "node": "^16.13 || >=18" + "node": ">=10.0.0" } }, - "node_modules/@wdio/globals/node_modules/devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/@zip.js/zip.js": { + "version": "2.7.48", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.48.tgz", + "integrity": "sha512-J7cliimZ2snAbr0IhLx2U8BwfA1pKucahKzTpFtYq4hEgKxwvFJcIjCIVNPwQpfVab7iVP+AKmoH1gidBlyhiQ==", "dev": true, - "optional": true + "engines": { + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.0" + } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "node_modules/abbrev": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", + "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", + "dev": true + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" + "event-target-shim": "^5.0.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=6.5" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "optional": true, - "peer": true, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", "dependencies": { - "debug": "4" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" }, "engines": { - "node": ">= 6.0.0" + "node": ">= 0.6" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.0" + "bin": { + "acorn": "bin/acorn" }, - "peerDependencies": { - "devtools-protocol": "*" + "engines": { + "node": ">=0.4.0" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "node_modules/acorn-walk": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", + "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", "dev": true, - "optional": true, - "peer": true + "engines": { + "node": ">=0.4.0" + } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/aes-decrypter": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", + "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" + "@babel/runtime": "^7.12.5", + "@videojs/vhs-utils": "^3.0.5", + "global": "^4.4.0", + "pkcs7": "^1.0.4" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "agent-base": "6", "debug": "4" }, "engines": { - "node": ">= 6" + "node": ">= 6.0.0" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "node_modules/ajv": { + "version": "6.12.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", + "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - }, - "engines": { - "node": ">=16.0.0" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "ajv": "^6.9.1" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", "dev": true, "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=0.4.2" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/ansi-colors": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", + "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "engines": { + "node": ">=6" } }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/ansi-cyan": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", + "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "ansi-wrap": "0.1.0" }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "optional": true, - "peer": true, + "dependencies": { + "type-fest": "^0.21.3" + }, "engines": { - "node": ">=10" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/globals/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/ansi-gray": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", + "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", "dev": true, - "optional": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "ansi-wrap": "0.1.0" }, "engines": { - "node": ">= 14" + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/ansi-red": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", + "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", "dev": true, - "optional": true, - "engines": { - "node": ">=8" + "dependencies": { + "ansi-wrap": "0.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "engines": { + "node": ">=8" } }, - "node_modules/@wdio/globals/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "ms": "2.0.0" + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@wdio/globals/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "optional": true, + "node_modules/ansi-wrap": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", + "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", "dev": true, - "optional": true, "dependencies": { - "brace-expansion": "^2.0.1" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 8" } }, - "node_modules/@wdio/globals/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/@wdio/globals/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/append-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", + "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", "dev": true, - "optional": true, "dependencies": { - "whatwg-url": "^5.0.0" + "buffer-equal": "^1.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/@wdio/globals/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "node_modules/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, - "optional": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "archiver-utils": "^5.0.2", + "async": "^3.2.4", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", + "readdir-glob": "^1.1.2", + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, "engines": { "node": ">= 14" } }, - "node_modules/@wdio/globals/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "node_modules/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, - "optional": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "glob": "^10.0.0", + "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", + "lazystream": "^1.0.0", + "lodash": "^4.17.15", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true + "node": ">= 14" + } + }, + "node_modules/archiver-utils/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@wdio/globals/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "node_modules/archiver-utils/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "optional": true + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@wdio/globals/node_modules/readable-stream": { + "node_modules/archiver-utils/node_modules/readable-stream": { "version": "4.5.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "optional": true, "dependencies": { "abort-controller": "^3.0.0", "buffer": "^6.0.3", @@ -5302,4334 +5483,1364 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@wdio/globals/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "dependencies": { - "type-fest": "^2.12.2" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/@wdio/globals/node_modules/string_decoder": { + "node_modules/archiver-utils/node_modules/string_decoder": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "optional": true, "dependencies": { "safe-buffer": "~5.2.0" } }, - "node_modules/@wdio/globals/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/archiver/node_modules/async": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true }, - "node_modules/@wdio/globals/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "node_modules/archiver/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "funding": [ { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" + "type": "github", + "url": "https://github.com/sponsors/feross" }, { - "type": "paypal", - "url": "https://paypal.me/faisalman" + "type": "patreon", + "url": "https://www.patreon.com/feross" }, { - "type": "github", - "url": "https://github.com/sponsors/faisalman" + "type": "consulting", + "url": "https://feross.org/support" } ], - "optional": true, - "peer": true, - "engines": { - "node": "*" - } - }, - "node_modules/@wdio/globals/node_modules/webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", - "dev": true, - "optional": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" - }, - "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/@wdio/globals/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/archiver/node_modules/buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true, - "optional": true, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=8.0.0" } }, - "node_modules/@wdio/globals/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/archiver/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, - "optional": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=12" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@wdio/globals/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "node_modules/archiver/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, - "optional": true, "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" + "safe-buffer": "~5.2.0" } }, - "node_modules/@wdio/local-runner": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.36.1.tgz", - "integrity": "sha512-FYsTzbNGRnrniOsLWrZO7+DLecAS9W75AIzFZQVQxruiDFkGmKY5OV6gsuvMlasaqAQXW1s+w29bqrLY4DxdEw==", + "node_modules/archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", + "dev": true + }, + "node_modules/are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", "dev": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/repl": "8.24.12", - "@wdio/runner": "8.36.1", - "@wdio/types": "8.36.1", - "async-exit-hook": "^2.0.1", - "split2": "^4.1.0", - "stream-buffers": "^3.0.2" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=14" } }, - "node_modules/@wdio/logger": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", - "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", - "dev": true, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dependencies": { - "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" - }, - "engines": { - "node": "^16.13 || >=18" + "sprintf-js": "~1.0.2" } }, - "node_modules/@wdio/logger/node_modules/chalk": { + "node_modules/aria-query": { "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@wdio/mocha-framework": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.36.1.tgz", - "integrity": "sha512-G0h5AeneMNtoh9CcVQ82OCKj0axxUOEotEcInDu8V6UJbUywNJVL/bdTMKdaq5i84Hnc+s1LUKmLvN95F+lHGA==", + "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", + "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", "dev": true, "dependencies": { - "@types/mocha": "^10.0.0", - "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "mocha": "^10.0.0" - }, - "engines": { - "node": "^16.13 || >=18" + "dequal": "^2.0.3" } }, - "node_modules/@wdio/protocols": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.32.0.tgz", - "integrity": "sha512-inLJRrtIGdTz/YPbcsvpSvPlYQFTVtF3OYBwAXhG2FiP1ZwE1CQNLP/xgRGye1ymdGCypGkexRqIx3KBGm801Q==", - "dev": true - }, - "node_modules/@wdio/repl": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", - "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "node_modules/arr-diff": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", + "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", "dev": true, "dependencies": { - "@types/node": "^20.1.0" + "arr-flatten": "^1.0.1", + "array-slice": "^0.2.3" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "node_modules/@wdio/reporter": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.36.1.tgz", - "integrity": "sha512-HcXr9XKq/6kPC9nexMRXIc/ft3Lvp0yCaW5tps01Axus9wbi5ysLHi2z5sB84F2YdpM+aRf7Lac56xkc4Jldeg==", + "node_modules/arr-diff/node_modules/array-slice": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", + "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", "dev": true, - "dependencies": { - "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "diff": "^5.0.0", - "object-inspect": "^1.12.0" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.36.1.tgz", - "integrity": "sha512-bLkxQ46MLEbzIf30adl2nyz8kxED/V0IjcQASm0VKfNmsG8LOf7iOIz+udOF4GkMoF++5JuONA5abUsyLvwatg==", + "node_modules/arr-filter": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", + "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", "dev": true, "dependencies": { - "@types/node": "^20.11.28", - "@wdio/config": "8.36.1", - "@wdio/globals": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "deepmerge-ts": "^5.1.0", - "expect-webdriverio": "^4.12.0", - "gaze": "^1.1.3", - "webdriver": "8.36.1", - "webdriverio": "8.36.1" + "make-iterator": "^1.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", "dev": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/arr-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", + "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "make-iterator": "^1.0.0" }, "engines": { - "node": ">= 14" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "node_modules/arr-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", + "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", "dev": true, - "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - }, "engines": { - "node": ">= 14" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "node_modules/array-buffer-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", + "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", "dev": true, "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" }, "engines": { - "node": ">= 14" - } - }, - "node_modules/@wdio/runner/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/@wdio/runner/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/buffer-crc32": { + "node_modules/array-differ": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", + "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", "dev": true, "engines": { - "node": ">=8.0.0" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", + "node_modules/array-each": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", + "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, "engines": { - "node": ">=12.13.0" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, + "node_modules/array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", + "dev": true + }, + "node_modules/array-includes": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", "dev": true, "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-object-atoms": "^1.0.0", + "get-intrinsic": "^1.2.4", + "is-string": "^1.0.7" }, "engines": { - "node": ">= 14" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "node_modules/array-initial": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", + "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", "dev": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "array-slice": "^1.0.0", + "is-number": "^4.0.0" }, "engines": { - "node": ">= 14" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/cross-fetch": { + "node_modules/array-initial/node_modules/is-number": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true, - "dependencies": { - "node-fetch": "^2.6.12" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "node_modules/array-last": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", + "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" + "is-number": "^4.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "node_modules/array-last/node_modules/is-number": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", + "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/array-slice": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", + "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "node_modules/array-sort": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", + "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "mitt": "3.0.0" + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" }, - "peerDependencies": { - "devtools-protocol": "*" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/array-sort/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "node_modules/array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true, - "optional": true, - "peer": true + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/array.prototype.findlastindex": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "call-bind": "^1.0.7", + "define-properties": "^1.2.1", + "es-abstract": "^1.23.2", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-shim-unscopables": "^1.0.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "node_modules/array.prototype.flat": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", + "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": ">= 0.4" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/array.prototype.flatmap": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", + "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "es-abstract": "^1.22.1", + "es-shim-unscopables": "^1.0.0" }, "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/arraybuffer.prototype.slice": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", + "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "array-buffer-byte-length": "^1.0.1", + "call-bind": "^1.0.5", + "define-properties": "^1.2.1", + "es-abstract": "^1.22.3", + "es-errors": "^1.2.1", + "get-intrinsic": "^1.2.3", + "is-array-buffer": "^3.0.4", + "is-shared-array-buffer": "^1.0.2" }, "engines": { - "node": ">=6" - } - }, - "node_modules/@wdio/runner/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/asn1": { + "version": "0.2.6", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", + "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, - "engines": { - "node": ">= 14" + "safer-buffer": "~2.1.0" } }, - "node_modules/@wdio/runner/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/assert": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "call-bind": "^1.0.2", + "is-nan": "^1.3.2", + "object-is": "^1.1.5", + "object.assign": "^4.1.4", + "util": "^0.12.5" } }, - "node_modules/@wdio/runner/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "engines": { + "node": ">=0.8" } }, - "node_modules/@wdio/runner/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" + "engines": { + "node": "*" } }, - "node_modules/@wdio/runner/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, + "node_modules/assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/@wdio/runner/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/ast-types": { + "version": "0.13.4", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", + "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "tslib": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/@wdio/runner/node_modules/ms": { + "node_modules/ast-types/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, + "node_modules/astral-regex": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", + "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "optional": true, - "peer": true + "engines": { + "node": ">=8" + } + }, + "node_modules/async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", + "dev": true }, - "node_modules/@wdio/runner/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/async-done": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", + "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "end-of-stream": "^1.1.0", + "once": "^1.3.2", + "process-nextick-args": "^2.0.0", + "stream-exhaust": "^1.0.1" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/@wdio/runner/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "node_modules/async-each": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", + "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/async-exit-hook": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", + "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", "dev": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - }, "engines": { - "node": ">= 14" + "node": ">=0.12.0" } }, - "node_modules/@wdio/runner/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "node_modules/async-settle": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", + "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", "dev": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "async-done": "^1.2.2" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 0.10" } }, - "node_modules/@wdio/runner/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true }, - "node_modules/@wdio/runner/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "node_modules/atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true, - "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "bin": { + "atob": "bin/atob.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 4.5.0" } }, - "node_modules/@wdio/runner/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, "dependencies": { - "type-fest": "^2.12.2" + "possible-typed-array-names": "^1.0.0" }, "engines": { - "node": ">=14.16" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@wdio/runner/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", "dev": true, - "dependencies": { - "safe-buffer": "~5.2.0" + "engines": { + "node": "*" } }, - "node_modules/@wdio/runner/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/aws4": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", + "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "dev": true + }, + "node_modules/axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@wdio/runner/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "node_modules/axios/node_modules/form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "optional": true, - "peer": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, "engines": { - "node": "*" + "node": ">= 6" } }, - "node_modules/@wdio/runner/node_modules/webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "node_modules/b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true + }, + "node_modules/babel-loader": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", + "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", "dev": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "find-cache-dir": "^3.3.1", + "loader-utils": "^2.0.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">= 8.9" }, "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } + "@babel/core": "^7.0.0", + "webpack": ">=2" } }, - "node_modules/@wdio/runner/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/babel-plugin-istanbul": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", + "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@istanbuljs/load-nyc-config": "^1.0.0", + "@istanbuljs/schema": "^0.1.2", + "istanbul-lib-instrument": "^5.0.4", + "test-exclude": "^6.0.0" + }, "engines": { - "node": ">=10.0.0" + "node": ">=8" + } + }, + "node_modules/babel-plugin-polyfill-corejs2": { + "version": "0.4.11", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", + "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", + "dependencies": { + "@babel/compat-data": "^7.22.6", + "@babel/helper-define-polyfill-provider": "^0.6.2", + "semver": "^6.3.1" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@wdio/runner/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, + "node_modules/babel-plugin-polyfill-corejs3": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", + "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "@babel/helper-define-polyfill-provider": "^0.6.1", + "core-js-compat": "^3.36.1" }, - "engines": { - "node": ">=12" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@wdio/runner/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, + "node_modules/babel-plugin-polyfill-regenerator": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", + "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@babel/helper-define-polyfill-provider": "^0.6.2" }, - "engines": { - "node": ">= 14" + "peerDependencies": { + "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" } }, - "node_modules/@wdio/spec-reporter": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.36.1.tgz", - "integrity": "sha512-VgAd8VQCfwKYz4A3BPDUYNIQxXhRSTaVNbmDzSlYfo5Jekygk7fz0LRFYBpJ69l7eQH0P5nzEyF92oW/rvE3VA==", + "node_modules/bach": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", + "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", "dev": true, "dependencies": { - "@wdio/reporter": "8.36.1", - "@wdio/types": "8.36.1", - "chalk": "^5.1.2", - "easy-table": "^1.2.0", - "pretty-ms": "^7.0.0" + "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" }, "engines": { - "node": "^16.13 || >=18" + "node": ">= 0.10" } }, - "node_modules/@wdio/spec-reporter/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "node_modules/bail": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", + "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/@wdio/types": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.36.1.tgz", - "integrity": "sha512-kKtyJbypasKo/VQuJ6dTQQwFtHE9qoygjoCZjrQCLGraRSjOEiqZHPR0497wbeCvcgHIYyImbmcylqZNGUE0CQ==", + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true + }, + "node_modules/bare-events": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", + "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", "dev": true, - "dependencies": { - "@types/node": "^20.1.0" - }, - "engines": { - "node": "^16.13 || >=18" - } + "optional": true }, - "node_modules/@wdio/utils": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.36.1.tgz", - "integrity": "sha512-xmgPHU11/o9n2FeRmDFkPRC0okiwA1i2xOcR2c3aSpuk99XkAm9RaMn/6u9LFaqsCpgaVxazcYEGSceO7U4hZA==", + "node_modules/bare-fs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", + "integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", "dev": true, + "optional": true, "dependencies": { - "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "decamelize": "^6.0.0", - "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.3.5", - "geckodriver": "^4.3.1", - "get-port": "^7.0.0", - "import-meta-resolve": "^4.0.0", - "locate-app": "^2.1.0", - "safaridriver": "^0.1.0", - "split2": "^4.2.0", - "wait-port": "^1.0.4" - }, - "engines": { - "node": "^16.13 || >=18" + "bare-events": "^2.0.0", + "bare-path": "^2.0.0", + "bare-stream": "^1.0.0" } }, - "node_modules/@webassemblyjs/ast": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", - "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", + "node_modules/bare-os": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", + "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", + "dev": true, + "optional": true + }, + "node_modules/bare-path": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.2.tgz", + "integrity": "sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==", "dev": true, + "optional": true, "dependencies": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "bare-os": "^2.1.0" } }, - "node_modules/@webassemblyjs/floating-point-hex-parser": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz", - "integrity": "sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-api-error": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz", - "integrity": "sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-buffer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz", - "integrity": "sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-numbers": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz", - "integrity": "sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g==", - "dev": true, - "dependencies": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/helper-wasm-bytecode": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz", - "integrity": "sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA==", - "dev": true - }, - "node_modules/@webassemblyjs/helper-wasm-section": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz", - "integrity": "sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" - } - }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", - "dev": true, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" - } - }, - "node_modules/@webassemblyjs/leb128": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.6.tgz", - "integrity": "sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ==", - "dev": true, - "dependencies": { - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@webassemblyjs/utf8": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.6.tgz", - "integrity": "sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA==", - "dev": true - }, - "node_modules/@webassemblyjs/wasm-edit": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz", - "integrity": "sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/helper-wasm-section": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-opt": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1", - "@webassemblyjs/wast-printer": "1.12.1" - } - }, - "node_modules/@webassemblyjs/wasm-gen": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz", - "integrity": "sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wasm-opt": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz", - "integrity": "sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" - } - }, - "node_modules/@webassemblyjs/wasm-parser": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz", - "integrity": "sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-api-error": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/ieee754": "1.11.6", - "@webassemblyjs/leb128": "1.11.6", - "@webassemblyjs/utf8": "1.11.6" - } - }, - "node_modules/@webassemblyjs/wast-printer": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz", - "integrity": "sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA==", - "dev": true, - "dependencies": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" - } - }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/@xtuc/ieee754": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", - "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", - "dev": true - }, - "node_modules/@xtuc/long": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", - "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", - "dev": true - }, - "node_modules/abbrev": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", - "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", - "dev": true - }, - "node_modules/abort-controller": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", - "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", - "dev": true, - "dependencies": { - "event-target-shim": "^5.0.0" - }, - "engines": { - "node": ">=6.5" - } - }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", - "dev": true, - "bin": { - "acorn": "bin/acorn" - }, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/acorn-jsx": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", - "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", - "dev": true, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" - } - }, - "node_modules/acorn-walk": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.2.tgz", - "integrity": "sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==", - "dev": true, - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/aes-decrypter": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/aes-decrypter/-/aes-decrypter-3.1.3.tgz", - "integrity": "sha512-VkG9g4BbhMBy+N5/XodDeV6F02chEk9IpgRTq/0bS80y4dzy79VH2Gtms02VXomf3HmyRe3yyJYkJ990ns+d6A==", - "dev": true, - "dependencies": { - "@babel/runtime": "^7.12.5", - "@videojs/vhs-utils": "^3.0.5", - "global": "^4.4.0", - "pkcs7": "^1.0.4" - } - }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dev": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.3", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.3.tgz", - "integrity": "sha512-4K0cK3L1hsqk9xIb2z9vs/XU+PGJZ9PNpJRDS9YLzmNdX6jmVPfamLvTJr0aDAusnHyCHO6MjzlkAsgtqp9teA==", - "dev": true, - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/amdefine": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", - "dev": true, - "optional": true, - "engines": { - "node": ">=0.4.2" - } - }, - "node_modules/ansi-colors": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", - "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-cyan": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-cyan/-/ansi-cyan-0.1.1.tgz", - "integrity": "sha512-eCjan3AVo/SxZ0/MyIYRtkpxIu/H3xZN7URr1vXVrISxeyz8fUFz0FJziamK4sS8I+t35y4rHg1b2PklyBe/7A==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.21.3" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ansi-gray": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-gray/-/ansi-gray-0.1.1.tgz", - "integrity": "sha512-HrgGIZUl8h2EHuZaU9hTR/cU5nhKxpVE1V6kdGsQ8e4zirElJ5fvtfc8N7Q1oq1aatO275i8pUFUCpNWCAnVWw==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-red": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ansi-red/-/ansi-red-0.1.1.tgz", - "integrity": "sha512-ewaIr5y+9CUTGFwZfpECUbFlGcC0GCw1oqR9RI6h1gQCd9Aj2GxSckCnPsVJnmfMZbwFYE+leZGASgkWl06Jow==", - "dev": true, - "dependencies": { - "ansi-wrap": "0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dependencies": { - "color-convert": "^1.9.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ansi-wrap": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ansi-wrap/-/ansi-wrap-0.1.0.tgz", - "integrity": "sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/append-buffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/append-buffer/-/append-buffer-1.0.2.tgz", - "integrity": "sha512-WLbYiXzD3y/ATLZFufV/rZvWdZOs+Z/+5v1rBZ463Jn398pa6kcde27cvozYnBoxXblGZTFfoPpsaEw0orU5BA==", - "dev": true, - "dependencies": { - "buffer-equal": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", - "dev": true, - "dependencies": { - "archiver-utils": "^2.1.0", - "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", - "dev": true, - "dependencies": { - "glob": "^7.1.4", - "graceful-fs": "^4.2.0", - "lazystream": "^1.0.0", - "lodash.defaults": "^4.2.0", - "lodash.difference": "^4.5.0", - "lodash.flatten": "^4.4.0", - "lodash.isplainobject": "^4.0.6", - "lodash.union": "^4.6.0", - "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver-utils/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/archiver/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "node_modules/archiver/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/archiver/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true - }, - "node_modules/argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dependencies": { - "sprintf-js": "~1.0.2" - } - }, - "node_modules/aria-query": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", - "integrity": "sha512-b0P0sZPKtyu8HkeRAfCq0IfURZK+SuwMjY1UXGBU27wpAiTwQAIlq56IbIO+ytk/JjS1fMR14ee5WBBfKi5J6A==", - "dev": true, - "dependencies": { - "dequal": "^2.0.3" - } - }, - "node_modules/arr-diff": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-1.1.0.tgz", - "integrity": "sha512-OQwDZUqYaQwyyhDJHThmzId8daf4/RFNLaeh3AevmSeZ5Y7ug4Ga/yKc6l6kTZOBW781rCj103ZuTh8GAsB3+Q==", - "dev": true, - "dependencies": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-diff/node_modules/array-slice": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-0.2.3.tgz", - "integrity": "sha512-rlVfZW/1Ph2SNySXwR9QYkChp8EkOEiTMO5Vwx60usw04i4nWemkm9RXmQqgkQFaLHsqLuADvjp6IfgL9l2M8Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-filter": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/arr-filter/-/arr-filter-1.1.2.tgz", - "integrity": "sha512-A2BETWCqhsecSvCkWAeVBFLH6sXEUGASuzkpjL3GR1SlL/PWL6M3J8EAAld2Uubmh39tvkJTqC9LeLHCUKmFXA==", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-map": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/arr-map/-/arr-map-2.0.2.tgz", - "integrity": "sha512-tVqVTHt+Q5Xb09qRkbu+DidW1yYzz5izWS2Xm2yFm7qJnmUfz4HPzNxbHkdRJbz2lrqI7S+z17xNYdFcBBO8Hw==", - "dev": true, - "dependencies": { - "make-iterator": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/arr-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-2.1.0.tgz", - "integrity": "sha512-t5db90jq+qdgk8aFnxEkjqta0B/GHrM1pxzuuZz2zWsOXc5nKu3t+76s/PQBA8FTcM/ipspIH9jWG4OxCBc2eA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-buffer-byte-length": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.1.tgz", - "integrity": "sha512-ahC5W1xgou+KTXix4sAO8Ki12Q+jf4i0+tmk3sC+zgcynshkHxzpXdImBehiUYKKKDwvfFiJl1tZt6ewscS1Mg==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.5", - "is-array-buffer": "^3.0.4" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha512-LeZY+DZDRnvP7eMuQ6LHfCzUGxAAIViUBliK24P3hWXL6y4SortgR6Nim6xrkfSLlmH0+k+9NYNwVC2s53ZrYQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-each": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/array-each/-/array-each-1.0.1.tgz", - "integrity": "sha512-zHjL5SZa68hkKHBFBK6DJCTtr9sfTCPCaph/L7tMSLcTFgy+zX7E+6q5UArbtOtMBCtxdICpfTCspRse+ywyXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" - }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha512-GQTc6Uupx1FCavi5mPzBvVT7nEOeWMmUA9P95wpfpW1XwMSKs+KaymD5C2Up7KAUKg/mYwbsUYzdZWcoajlNZg==", - "dev": true - }, - "node_modules/array-includes": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", - "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-object-atoms": "^1.0.0", - "get-intrinsic": "^1.2.4", - "is-string": "^1.0.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array-initial": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-initial/-/array-initial-1.1.0.tgz", - "integrity": "sha512-BC4Yl89vneCYfpLrs5JU2aAu9/a+xWbeKhvISg9PT7eWFB9UlRvI+rKEtk6mgxWr3dSkk9gQ8hCrdqt06NXPdw==", - "dev": true, - "dependencies": { - "array-slice": "^1.0.0", - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-initial/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/array-last/-/array-last-1.3.0.tgz", - "integrity": "sha512-eOCut5rXlI6aCOS7Z7kCplKRKyiFQ6dHFBem4PwlwKeNFk2/XxTrhRh5T9PyaEWGy/NHTZWbY+nsZlNFJu9rYg==", - "dev": true, - "dependencies": { - "is-number": "^4.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-last/node_modules/is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-slice": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/array-slice/-/array-slice-1.1.0.tgz", - "integrity": "sha512-B1qMD3RBP7O8o0H2KbrXDyB0IccejMF15+87Lvlor12ONPRHP6gTjXMNkt/d3ZuOGbAe66hFmaCfECI24Ufp6w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-sort": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-sort/-/array-sort-1.0.0.tgz", - "integrity": "sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg==", - "dev": true, - "dependencies": { - "default-compare": "^1.0.0", - "get-value": "^2.0.6", - "kind-of": "^5.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/array.prototype.findlastindex": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", - "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.7", - "define-properties": "^1.2.1", - "es-abstract": "^1.23.2", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-shim-unscopables": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flat": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.2.tgz", - "integrity": "sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/array.prototype.flatmap": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.2.tgz", - "integrity": "sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "define-properties": "^1.2.0", - "es-abstract": "^1.22.1", - "es-shim-unscopables": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/arraybuffer.prototype.slice": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.3.tgz", - "integrity": "sha512-bMxMKAjg13EBSVscxTaYA4mRc5t1UAXa2kXiGTNfZ079HIWXEkKmkgFrh/nJqamaLSrXO5H4WFFkPEaLJWbs3A==", - "dev": true, - "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "call-bind": "^1.0.5", - "define-properties": "^1.2.1", - "es-abstract": "^1.22.3", - "es-errors": "^1.2.1", - "get-intrinsic": "^1.2.3", - "is-array-buffer": "^3.0.4", - "is-shared-array-buffer": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "dev": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } - }, - "node_modules/assert": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", - "dev": true, - "dependencies": { - "call-bind": "^1.0.2", - "is-nan": "^1.3.2", - "object-is": "^1.1.5", - "object.assign": "^4.1.4", - "util": "^0.12.5" - } - }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "dev": true, - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/ast-types/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", - "dev": true - }, - "node_modules/astral-regex": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", - "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/async": { - "version": "1.5.2", - "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", - "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", - "dev": true - }, - "node_modules/async-done": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/async-done/-/async-done-1.3.2.tgz", - "integrity": "sha512-uYkTP8dw2og1tu1nmza1n1CMW0qb8gWWlwqMmLb7MhBVs4BXrFziT6HXUd+/RlRA/i4H9AkofYloUbs1fwMqlw==", - "dev": true, - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.2", - "process-nextick-args": "^2.0.0", - "stream-exhaust": "^1.0.1" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/async-each": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/async-each/-/async-each-1.0.6.tgz", - "integrity": "sha512-c646jH1avxr+aVpndVMeAfYw7wAa6idufrlN3LPA4PmKS0QEGp6PIC9nwz0WQkkvBGAMEki3pFdtxaF39J9vvg==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ] - }, - "node_modules/async-exit-hook": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/async-exit-hook/-/async-exit-hook-2.0.1.tgz", - "integrity": "sha512-NW2cX8m1Q7KPA7a5M2ULQeZ2wR5qI5PAbw5L0UOMxdioVk9PMZ0h1TmyZEkPYrCvYjDlFICusOu1dlEKAAeXBw==", - "dev": true, - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/async-settle": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-settle/-/async-settle-1.0.0.tgz", - "integrity": "sha512-VPXfB4Vk49z1LHHodrEQ6Xf7W4gg1w0dAPROHngx7qgDjqmIQ+fXmwgGXTW/ITLai0YLSvWepJOP9EVpMnEAcw==", - "dev": true, - "dependencies": { - "async-done": "^1.2.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", - "dev": true - }, - "node_modules/atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "dev": true, - "bin": { - "atob": "bin/atob.js" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", - "dev": true - }, - "node_modules/b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "dev": true - }, - "node_modules/babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "dependencies": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "node_modules/babel-code-frame/node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "dependencies": { - "ansi-regex": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-code-frame/node_modules/supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true, - "engines": { - "node": ">=0.8.0" - } - }, - "node_modules/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, - "dependencies": { - "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" - } - }, - "node_modules/babel-core/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "node_modules/babel-core/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-core/node_modules/json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true, - "bin": { - "json5": "lib/cli.js" - } - }, - "node_modules/babel-core/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "dependencies": { - "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" - } - }, - "node_modules/babel-generator/node_modules/jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true, - "bin": { - "jsesc": "bin/jsesc" - } - }, - "node_modules/babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "node_modules/babel-loader": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.3.0.tgz", - "integrity": "sha512-H8SvsMF+m9t15HNLMipppzkC+Y2Yq+v3SonZyU70RBL/h1gxPkH08Ot8pEE9Z4Kd+czyWJClmFS8qzIP9OZ04Q==", - "dev": true, - "dependencies": { - "find-cache-dir": "^3.3.1", - "loader-utils": "^2.0.0", - "make-dir": "^3.1.0", - "schema-utils": "^2.6.5" - }, - "engines": { - "node": ">= 8.9" - }, - "peerDependencies": { - "@babel/core": "^7.0.0", - "webpack": ">=2" - } - }, - "node_modules/babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.22.0" - } - }, - "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", - "dev": true, - "dependencies": { - "@babel/helper-plugin-utils": "^7.0.0", - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", - "test-exclude": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-polyfill-corejs2": { - "version": "0.4.11", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.4.11.tgz", - "integrity": "sha512-sMEJ27L0gRHShOh5G54uAAPaiCOygY/5ratXuiyb2G46FmlSpc9eFCzYVyDiPxfNbwzA7mYahmjQc5q+CZQ09Q==", - "dependencies": { - "@babel/compat-data": "^7.22.6", - "@babel/helper-define-polyfill-provider": "^0.6.2", - "semver": "^6.3.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-corejs3": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.10.4.tgz", - "integrity": "sha512-25J6I8NGfa5YkCDogHRID3fVCadIR8/pGl1/spvCkzb6lVn6SR3ojpx9nOn9iEBcUsjY24AmdKm5khcfKdylcg==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.1", - "core-js-compat": "^3.36.1" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-plugin-polyfill-regenerator": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.6.2.tgz", - "integrity": "sha512-2R25rQZWP63nGwaAswvDazbPXfrM3HwVoBXK6HcqeKrSrL/JqcC/rDcf95l4r7LXLyxDXc8uQDa064GubtCABg==", - "dependencies": { - "@babel/helper-define-polyfill-provider": "^0.6.2" - }, - "peerDependencies": { - "@babel/core": "^7.4.0 || ^8.0.0-0 <8.0.0" - } - }, - "node_modules/babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "dev": true, - "dependencies": { - "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" - } - }, - "node_modules/babel-register/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "dependencies": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "node_modules/babel-runtime/node_modules/core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "deprecated": "core-js@<3.23.3 is no longer maintained and not recommended for usage due to the number of issues. Because of the V8 engine whims, feature detection in old core-js versions could cause a slowdown up to 100x even if nothing is polyfilled. Some versions have web compatibility issues. Please, upgrade your dependencies to the actual version of core-js.", - "dev": true, - "hasInstallScript": true - }, - "node_modules/babel-runtime/node_modules/regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "node_modules/babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "node_modules/babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "dev": true, - "dependencies": { - "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" - } - }, - "node_modules/babel-traverse/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/babel-traverse/node_modules/globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babel-traverse/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "dev": true, - "dependencies": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "node_modules/babel-types/node_modules/to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true, - "bin": { - "babylon": "bin/babylon.js" - } - }, - "node_modules/bach": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", - "integrity": "sha512-bZOOfCb3gXBXbTFXq3OZtGR88LwGeJvzu6szttaIzymOTS4ZttBNOWSv7aLZja2EMycKtRYV0Oa8SNKH/zkxvg==", - "dev": true, - "dependencies": { - "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" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/bail": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/bail/-/bail-2.0.2.tgz", - "integrity": "sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true - }, - "node_modules/bare-events": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.2.2.tgz", - "integrity": "sha512-h7z00dWdG0PYOQEvChhOSWvOfkIKsdZGkWr083FgN/HyoQuebSew/cgirYqh9SCuy/hRvxc5Vy6Fw8xAmYHLkQ==", - "dev": true, - "optional": true - }, - "node_modules/bare-fs": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.0.tgz", - "integrity": "sha512-TNFqa1B4N99pds2a5NYHR15o0ZpdNKbAeKTE/+G6ED/UeOavv8RY3dr/Fu99HW3zU3pXpo2kDNO8Sjsm2esfOw==", - "dev": true, - "optional": true, - "dependencies": { - "bare-events": "^2.0.0", - "bare-path": "^2.0.0", - "bare-stream": "^1.0.0" - } - }, - "node_modules/bare-os": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-2.3.0.tgz", - "integrity": "sha512-oPb8oMM1xZbhRQBngTgpcQ5gXw6kjOaRsSWsIeNyRxGed2w/ARyP7ScBYpWR1qfX2E5rS3gBw6OWcSQo+s+kUg==", - "dev": true, - "optional": true - }, - "node_modules/bare-path": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.2.tgz", - "integrity": "sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==", - "dev": true, - "optional": true, - "dependencies": { - "bare-os": "^2.1.0" - } - }, - "node_modules/bare-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", - "integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", - "dev": true, - "optional": true, - "dependencies": { - "streamx": "^2.16.1" - } - }, - "node_modules/base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "dependencies": { - "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" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base/node_modules/define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", - "dev": true, - "dependencies": { - "is-descriptor": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } - }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", - "dev": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } - }, - "node_modules/beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/big-integer": { - "version": "1.6.52", - "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", - "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", - "dev": true, - "engines": { - "node": ">=0.6" - } - }, - "node_modules/big.js": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", - "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/binary": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", - "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", - "dev": true, - "dependencies": { - "buffers": "~0.1.1", - "chainsaw": "~0.1.0" - }, - "engines": { - "node": "*" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", - "dev": true, - "engines": { - "node": ">=0.8" - }, - "funding": { - "url": "https://bevry.me/fund" - } - }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "dev": true, - "optional": true, - "dependencies": { - "file-uri-to-path": "1.0.0" - } - }, - "node_modules/bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "dependencies": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - } - }, - "node_modules/bl/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/bluebird": { - "version": "3.7.2", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", - "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" - }, - "node_modules/body": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", - "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", - "dev": true, - "dependencies": { - "continuable-cache": "^0.3.1", - "error": "^7.0.0", - "raw-body": "~1.1.0", - "safe-json-parse": "~1.0.1" - } - }, - "node_modules/body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "http-errors": "2.0.0", - "iconv-lite": "0.4.24", - "on-finished": "2.4.1", - "qs": "6.11.0", - "raw-body": "2.5.2", - "type-is": "~1.6.18", - "unpipe": "1.0.0" - }, - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } - }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - }, - "node_modules/body/node_modules/bytes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", - "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", - "dev": true - }, - "node_modules/body/node_modules/raw-body": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", - "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", - "dev": true, - "dependencies": { - "bytes": "1", - "string_decoder": "0.10" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/body/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, - "node_modules/boolbase": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", - "dev": true - }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "node_modules/browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" - }, - "bin": { - "browserslist": "cli.js" - }, - "engines": { - "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" - } - }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1" - } - }, - "node_modules/browserstack-local": { - "version": "1.5.5", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.5.tgz", - "integrity": "sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==", - "dev": true, - "dependencies": { - "agent-base": "^6.0.2", - "https-proxy-agent": "^5.0.1", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - } - }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/browserstacktunnel-wrapper": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.5.tgz", - "integrity": "sha512-oociT3nl+FhQnyJbAb1RM4oQ5pN7aKeXEURkTkiEVm/Rji2r0agl3Wbw5V23VFn9lCU5/fGyDejRZPtGYsEcFw==", - "dev": true, - "dependencies": { - "https-proxy-agent": "^2.2.1", - "unzipper": "^0.9.3" - }, - "engines": { - "node": ">= 0.10.20" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true - }, - "node_modules/browserstacktunnel-wrapper/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "dependencies": { - "readable-stream": "^2.0.2" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" - } - }, - "node_modules/browserstacktunnel-wrapper/node_modules/unzipper": { - "version": "0.9.15", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", - "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "binary": "~0.3.0", - "bluebird": "~3.4.1", - "buffer-indexof-polyfill": "~1.0.0", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "listenercount": "~1.0.1", - "readable-stream": "~2.3.6", - "setimmediate": "~1.0.4" - } - }, - "node_modules/buffer": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", - "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.1.13" - } - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "dev": true, - "engines": { - "node": "*" - } - }, - "node_modules/buffer-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", - "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", - "dev": true, - "engines": { - "node": ">=0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/buffer-from": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", - "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", - "dev": true - }, - "node_modules/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, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/buffers": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", - "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", - "dev": true, - "engines": { - "node": ">=0.2.0" - } - }, - "node_modules/bufferstreams": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", - "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", - "dependencies": { - "readable-stream": "^1.0.33" - }, - "engines": { - "node": ">= 0.10.0" - } - }, - "node_modules/bufferstreams/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" - }, - "node_modules/bufferstreams/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "node_modules/bufferstreams/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" - }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, - "dependencies": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "dependencies": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/call-bind": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/can-autoplay": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz", - "integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA==", - "dev": true - }, - "node_modules/caniuse-lite": { - "version": "1.0.30001620", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", - "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==", - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/caniuse-lite" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ] - }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", - "dev": true - }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chai": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", - "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", - "dev": true, - "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.3", - "deep-eql": "^4.1.3", - "get-func-name": "^2.0.2", - "loupe": "^2.3.6", - "pathval": "^1.1.1", - "type-detect": "^4.0.8" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/chainsaw": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", - "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", - "dev": true, - "dependencies": { - "traverse": ">=0.3.0 <0.4" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/character-entities": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", - "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "dev": true - }, - "node_modules/check-error": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", - "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", - "dev": true, - "dependencies": { - "get-func-name": "^2.0.2" - }, - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "node_modules/chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" - }, - "engines": { - "node": ">=12.13.0" - } - }, - "node_modules/chrome-launcher/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", - "dev": true, - "engines": { - "node": ">=6.0" - } - }, - "node_modules/chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, - "dependencies": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", - "dev": true, - "dependencies": { - "is-descriptor": "^0.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/class-utils/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", - "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "dev": true, - "dependencies": { - "restore-cursor": "^3.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", - "dev": true, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "engines": { - "node": ">= 12" - } - }, - "node_modules/cliui": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", - "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^7.0.0" - }, - "engines": { - "node": ">=12" - } - }, - "node_modules/cliui/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cliui/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cliui/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", - "dev": true, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", - "dev": true, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", - "dev": true, - "dependencies": { - "mimic-response": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/clone-response/node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/clone-stats": { + "node_modules/bare-stream": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", - "dev": true - }, - "node_modules/cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-1.0.0.tgz", + "integrity": "sha512-KhNUoDL40iP4gFaLSsoGE479t0jHijfYdIcxRn/XtezA2BaUD0NRf/JGRpsMq6dMNM+SrCrB0YSSo/5wBY4rOQ==", "dev": true, + "optional": true, "dependencies": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - } - }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", - "dev": true, - "engines": { - "node": ">=0.10.0" + "streamx": "^2.16.1" } }, - "node_modules/collection-map": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", - "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", + "node_modules/base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "dependencies": { - "arr-map": "^2.0.2", - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "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" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/collection-visit": { + "node_modules/base/node_modules/define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", "dev": true, "dependencies": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "is-descriptor": "^1.0.0" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dependencies": { - "color-name": "1.1.3" - } - }, - "node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "dev": true, - "bin": { - "color-support": "bin.js" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "node_modules/colors": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", - "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", "dev": true, "engines": { - "node": ">=0.1.90" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "node_modules/basic-auth": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", + "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", "dev": true, "dependencies": { - "delayed-stream": "~1.0.0" + "safe-buffer": "5.1.2" }, "engines": { "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true - }, - "node_modules/comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", - "dev": true, - "engines": { - "node": ">= 12.0.0" - } - }, - "node_modules/commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "node_modules/basic-auth/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true }, - "node_modules/component-emitter": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", - "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", - "dev": true, - "dependencies": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", - "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/compress-commons/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/basic-ftp": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", + "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, "engines": { - "node": ">= 6" + "node": ">=10.0.0" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "node_modules/batch": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", + "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", "dev": true }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "engines": [ - "node >= 0.8" - ], - "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "node_modules/concat-with-sourcemaps": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", - "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", + "node_modules/bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", "dev": true, "dependencies": { - "source-map": "^0.6.1" + "tweetnacl": "^0.14.3" } }, - "node_modules/concat-with-sourcemaps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/beeper": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", + "integrity": "sha512-3vqtKL1N45I5dV0RdssXZG7X6pCqQrWPNOlBPZPrd+QkE2HEhR57Z04m0KtpbsZH73j+a3F8UD1TQnn+ExTvIA==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/big-integer": { + "version": "1.6.52", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", + "integrity": "sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==", "dev": true, - "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, "engines": { - "node": ">= 0.10.0" + "node": ">=0.6" } }, - "node_modules/connect-livereload": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", - "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", "dev": true, "engines": { "node": "*" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/binary": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz", + "integrity": "sha512-D4H1y5KYwpJgK8wk1Cue5LLPgmwHKYSChkbspQg5JtVuR5ulGckxfR62H3AE9UDkdMC8yyXlqYihuz3Aqg2XZg==", "dev": true, "dependencies": { - "ms": "2.0.0" + "buffers": "~0.1.1", + "chainsaw": "~0.1.0" + }, + "engines": { + "node": "*" } }, - "node_modules/connect/node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", "dev": true, - "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, "engines": { - "node": ">= 0.8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/connect/node_modules/on-finished": { + "node_modules/binaryextensions": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==", "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, "engines": { - "node": ">= 0.8" + "node": ">=0.8" + }, + "funding": { + "url": "https://bevry.me/fund" } }, - "node_modules/connect/node_modules/statuses": { + "node_modules/bindings": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", "dev": true, - "engines": { - "node": ">= 0.6" + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" } }, - "node_modules/consolidate": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", - "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", - "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", + "node_modules/bluebird": { + "version": "3.7.2", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", + "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" + }, + "node_modules/body": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/body/-/body-5.1.0.tgz", + "integrity": "sha512-chUsBxGRtuElD6fmw1gHLpvnKdVLK302peeFa9ZqAEk8TyzZ3fygLyUEDDPTJvL9+Bor0dIwn6ePOsRM2y0zQQ==", + "dev": true, "dependencies": { - "bluebird": "^3.1.1" - }, - "engines": { - "node": ">= 0.10.0" + "continuable-cache": "^0.3.1", + "error": "^7.0.0", + "raw-body": "~1.1.0", + "safe-json-parse": "~1.0.1" } }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/body-parser": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "dependencies": { - "safe-buffer": "5.2.1" + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.13.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "engines": { - "node": ">= 0.6" + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" } }, - "node_modules/continuable-cache": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", - "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", - "dev": true - }, - "node_modules/convert-source-map": { + "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "node_modules/cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", - "engines": { - "node": ">= 0.6" - } + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "node_modules/body/node_modules/bytes": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", + "integrity": "sha512-/x68VkHLeTl3/Ll8IvxdwzhrT+IyKc52e/oyHhA2RwqPqswSnjVbSddfPRwAsJtbilMAPSRWwAlpxdYsSWOTKQ==", + "dev": true }, - "node_modules/copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", + "node_modules/body/node_modules/raw-body": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-1.1.7.tgz", + "integrity": "sha512-WmJJU2e9Y6M5UzTOkHaM7xJGAPQD8PNzx3bAd2+uhZAim6wDk6dAZxPVYLF67XhbR4hmKGh33Lpmh4XWrCH5Mg==", "dev": true, + "dependencies": { + "bytes": "1", + "string_decoder": "0.10" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.8.0" } }, - "node_modules/copy-props": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", - "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", - "dev": true, - "dependencies": { - "each-props": "^1.3.2", - "is-plain-object": "^5.0.0" - } + "node_modules/body/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true }, - "node_modules/core-js": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", - "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true }, - "node_modules/core-js-compat": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", - "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, "dependencies": { - "browserslist": "^4.23.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" - } - }, - "node_modules/core-js-pure": { - "version": "3.37.1", - "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", - "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", - "hasInstallScript": true, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/core-js" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "dependencies": { - "object-assign": "^4", - "vary": "^1" + "fill-range": "^7.1.1" }, "engines": { - "node": ">= 0.10" + "node": ">=8" } }, - "node_modules/coveralls": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", - "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", - "dev": true, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "node_modules/browserslist": { + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], "dependencies": { - "js-yaml": "^3.13.1", - "lcov-parse": "^1.0.0", - "log-driver": "^1.2.7", - "minimist": "^1.2.5", - "request": "^2.88.2" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" }, "bin": { - "coveralls": "bin/coveralls.js" + "browserslist": "cli.js" }, "engines": { - "node": ">=6" + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", + "node_modules/browserstack": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", + "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", "dev": true, - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" + "dependencies": { + "https-proxy-agent": "^2.2.1" } }, - "node_modules/crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "node_modules/browserstack-local": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.5.5.tgz", + "integrity": "sha512-jKne7yosrMcptj3hqxp36TP9k0ZW2sCqhyurX24rUL4G3eT7OLgv+CSQN8iq5dtkv5IK+g+v8fWvsiC/S9KxMg==", "dev": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" - }, - "engines": { - "node": ">= 10" + "agent-base": "^6.0.2", + "https-proxy-agent": "^5.0.1", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" } }, - "node_modules/crc32-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "es6-promisify": "^5.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 4.0.0" } }, - "node_modules/criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" - }, - "node_modules/cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "node-fetch": "2.6.7" + "ms": "^2.1.1" } }, - "node_modules/cross-fetch/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", "dev": true, "dependencies": { - "whatwg-url": "^5.0.0" + "agent-base": "^4.3.0", + "debug": "^3.1.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 4.5.0" } }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "node_modules/browserstacktunnel-wrapper": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/browserstacktunnel-wrapper/-/browserstacktunnel-wrapper-2.0.5.tgz", + "integrity": "sha512-oociT3nl+FhQnyJbAb1RM4oQ5pN7aKeXEURkTkiEVm/Rji2r0agl3Wbw5V23VFn9lCU5/fGyDejRZPtGYsEcFw==", "dev": true, "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" + "https-proxy-agent": "^2.2.1", + "unzipper": "^0.9.3" }, "engines": { - "node": ">= 8" + "node": ">= 0.10.20" } }, - "node_modules/cross-spawn/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/cross-spawn/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/browserstacktunnel-wrapper/node_modules/agent-base": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", + "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "es6-promisify": "^5.0.0" }, "engines": { - "node": ">= 8" + "node": ">= 4.0.0" } }, - "node_modules/crypto-js": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", - "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + "node_modules/browserstacktunnel-wrapper/node_modules/bluebird": { + "version": "3.4.7", + "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", + "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", + "dev": true }, - "node_modules/css": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", - "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", + "node_modules/browserstacktunnel-wrapper/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "inherits": "^2.0.4", - "source-map": "^0.6.1", - "source-map-resolve": "^0.6.0" + "ms": "^2.1.1" } }, - "node_modules/css-select": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", - "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", + "node_modules/browserstacktunnel-wrapper/node_modules/duplexer2": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", + "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", "dev": true, "dependencies": { - "boolbase": "^1.0.0", - "css-what": "^6.1.0", - "domhandler": "^5.0.2", - "domutils": "^3.0.1", - "nth-check": "^2.0.1" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "readable-stream": "^2.0.2" } }, - "node_modules/css-shorthand-properties": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", - "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", - "dev": true + "node_modules/browserstacktunnel-wrapper/node_modules/https-proxy-agent": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", + "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", + "dev": true, + "dependencies": { + "agent-base": "^4.3.0", + "debug": "^3.1.0" + }, + "engines": { + "node": ">= 4.5.0" + } }, - "node_modules/css-value": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", - "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", - "dev": true + "node_modules/browserstacktunnel-wrapper/node_modules/unzipper": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.9.15.tgz", + "integrity": "sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==", + "dev": true, + "dependencies": { + "big-integer": "^1.6.17", + "binary": "~0.3.0", + "bluebird": "~3.4.1", + "buffer-indexof-polyfill": "~1.0.0", + "duplexer2": "~0.1.4", + "fstream": "^1.0.12", + "listenercount": "~1.0.1", + "readable-stream": "~2.3.6", + "setimmediate": "~1.0.4" + } }, - "node_modules/css-what": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", - "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "dev": true, - "engines": { - "node": ">= 6" - }, - "funding": { - "url": "https://github.com/sponsors/fb55" + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" } }, - "node_modules/css/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": "*" } }, - "node_modules/csv-writer": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", - "integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==", - "dev": true - }, - "node_modules/custom-event": { + "node_modules/buffer-equal": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", - "dev": true - }, - "node_modules/d": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", - "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-1.0.1.tgz", + "integrity": "sha512-QoV3ptgEaQpvVwbXdSO39iqPQTCxSF7A5U99AxbHYqUdCizL/lH2Z0A2y6nbZucxMEOtNyZfG2s6gsVugGpKkg==", "dev": true, - "dependencies": { - "es5-ext": "^0.10.64", - "type": "^2.7.2" - }, "engines": { - "node": ">=0.12" + "node": ">=0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "dev": true + }, + "node_modules/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, - "dependencies": { - "assert-plus": "^1.0.0" - }, "engines": { "node": ">=0.10" } }, - "node_modules/data-uri-to-buffer": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", - "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", + "node_modules/buffers": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", + "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==", "dev": true, "engines": { - "node": ">= 12" + "node": ">=0.2.0" } }, - "node_modules/data-view-buffer": { + "node_modules/bufferstreams": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", - "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", - "dev": true, + "resolved": "https://registry.npmjs.org/bufferstreams/-/bufferstreams-1.0.1.tgz", + "integrity": "sha512-LZmiIfQprMLS6/k42w/PTc7awhU8AdNNcUerxTgr01WlP9agR2SgMv0wjlYYFD6eDOi8WvofrTX8RayjR/AeUQ==", "dependencies": { - "call-bind": "^1.0.6", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "readable-stream": "^1.0.33" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 0.10.0" } }, - "node_modules/data-view-byte-length": { + "node_modules/bufferstreams/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + }, + "node_modules/bufferstreams/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" + } + }, + "node_modules/bufferstreams/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==" + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cache-base": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", - "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "dependencies": { - "call-bind": "^1.0.7", - "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">=0.10.0" } }, - "node_modules/data-view-byte-offset": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", - "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", - "dev": true, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", "dependencies": { - "call-bind": "^1.0.6", + "es-define-property": "^1.0.0", "es-errors": "^1.3.0", - "is-data-view": "^1.0.1" + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" }, "engines": { "node": ">= 0.4" @@ -9638,3898 +6849,3796 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/date-format": { - "version": "4.0.14", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", - "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", "dev": true, "engines": { - "node": "*" + "node": ">=6" } }, - "node_modules/de-indent": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", - "dev": true, - "optional": true + "node_modules/can-autoplay": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/can-autoplay/-/can-autoplay-3.0.2.tgz", + "integrity": "sha512-Ih6wc7yJB4TylS/mLyAW0Dj5Nh3Gftq/g966TcxgvpNCOzlbqTs85srAq7mwIspo4w8gnLCVVGroyCHfh6l9aA==", + "dev": true }, - "node_modules/debounce": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", - "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", + "node_modules/caniuse-lite": { + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true }, - "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chai": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.4.1.tgz", + "integrity": "sha512-13sOfMv2+DWduEU+/xbun3LScLoqN17nBeTLUsmDfKdoiC1fr0n9PU4guu4AhRcOVFk/sW8LyZWHuhWtQZiF+g==", + "dev": true, "dependencies": { - "ms": "2.1.2" + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.0.8" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/debug-fabulous": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", - "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", + "node_modules/chainsaw": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz", + "integrity": "sha512-75kWfWt6MEKNC8xYXIdRpDehRYY/tNSgwKaJq+dbbDcxORuVrrQ+SEHoWsniVn9XPYfP4gmdWIeDk/4YNp1rNQ==", "dev": true, "dependencies": { - "debug": "3.X", - "memoizee": "0.4.X", - "object-assign": "4.X" + "traverse": ">=0.3.0 <0.4" + }, + "engines": { + "node": "*" } }, - "node_modules/debug-fabulous/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/decamelize": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", - "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", + "node_modules/character-entities": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", + "integrity": "sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==", "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/decode-named-character-reference": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", - "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", "dev": true, - "dependencies": { - "character-entities": "^2.0.0" - }, "funding": { "type": "github", "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "dev": true + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, + "dependencies": { + "get-func-name": "^2.0.2" + }, "engines": { - "node": ">=0.10" + "node": "*" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", "dev": true, "dependencies": { - "mimic-response": "^3.1.0" + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" }, "engines": { - "node": ">=10" + "node": ">=18.17" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" } }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "node_modules/cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "node_modules/cheerio/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", "dev": true, "dependencies": { - "type-detect": "^4.0.0" + "entities": "^4.5.0" }, - "engines": { - "node": ">=6" + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" } }, - "node_modules/deep-equal": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", - "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.0", - "call-bind": "^1.0.5", - "es-get-iterator": "^1.1.3", - "get-intrinsic": "^1.2.2", - "is-arguments": "^1.1.1", - "is-array-buffer": "^3.0.2", - "is-date-object": "^1.0.5", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.2", - "isarray": "^2.0.5", - "object-is": "^1.1.5", - "object-keys": "^1.1.1", - "object.assign": "^4.1.4", - "regexp.prototype.flags": "^1.5.1", - "side-channel": "^1.0.4", - "which-boxed-primitive": "^1.0.2", - "which-collection": "^1.0.1", - "which-typed-array": "^1.1.13" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">= 0.4" + "node": ">= 8.10.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/deep-is": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", - "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", - "dev": true - }, - "node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "node_modules/chrome-trace-event": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=6.0" } }, - "node_modules/deepmerge-ts": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", - "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", + "node_modules/ci-info": { + "version": "3.9.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", + "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/sibiraj-s" + } + ], "engines": { - "node": ">=16.0.0" + "node": ">=8" } }, - "node_modules/default-compare": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", - "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", + "node_modules/class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "dependencies": { - "kind-of": "^5.0.2" + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/default-resolution": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", - "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", + "node_modules/class-utils/node_modules/arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==", "dev": true, "engines": { - "node": ">= 0.10" + "node": ">=0.10.0" } }, - "node_modules/defaults": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", - "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "node_modules/class-utils/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", "dev": true, "dependencies": { - "clone": "^1.0.2" + "is-descriptor": "^0.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/defaults/node_modules/clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "node_modules/class-utils/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, + "dependencies": { + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" + }, "engines": { - "node": ">=0.8" + "node": ">= 0.4" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, + "dependencies": { + "restore-cursor": "^3.1.0" + }, "engines": { - "node": ">=10" + "node": ">=8" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, + "node_modules/cli-spinners": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.1.0.tgz", + "integrity": "sha512-2MH0N34TpDAs9ROPVkZJfBWFoYfv4zfkJF14PBHY4v/qRY75SLcm4WaEKNCLScsXieosa/tY/+slJ+BDswJxHQ==", + "dev": true, "engines": { - "node": ">= 0.4" + "node": ">=18.20" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-properties": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", - "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, - "dependencies": { - "define-data-property": "^1.0.1", - "has-property-descriptors": "^1.0.0", - "object-keys": "^1.1.1" - }, "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "node": ">= 12" } }, - "node_modules/define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", "dev": true, "dependencies": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=12" } }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", + "node_modules/cliui/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" + "color-convert": "^2.0.1" }, "engines": { - "node": ">= 14" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/degenerator/node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "node_modules/cliui/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "color-name": "~1.1.4" }, "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" + "node": ">=7.0.0" } }, - "node_modules/degenerator/node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "node_modules/cliui/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" + "dependencies": { + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=4" + "node": ">=8" } }, - "node_modules/degenerator/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, "engines": { - "node": ">=4.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "node_modules/degenerator/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", "dev": true, - "optional": true, "engines": { - "node": ">=0.10.0" + "node": ">=0.8" } }, - "node_modules/delayed-stream": { + "node_modules/clone-buffer": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", "dev": true, "engines": { - "node": ">=0.4.0" + "node": ">= 0.10" } }, - "node_modules/depd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", - "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "node_modules/clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "dependencies": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + }, "engines": { - "node": ">= 0.8" + "node": ">=6" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "node_modules/clone-deep/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, + "dependencies": { + "isobject": "^3.0.1" + }, "engines": { - "node": ">=6" + "node": ">=0.10.0" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "node_modules/clone-response/node_modules/mimic-response": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", + "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "extraneous": true, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=4" } }, - "node_modules/detect-file": { + "node_modules/clone-stats": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", - "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha512-au6ydSpg6nsrigcZ4m8Bc9hxjeW+GJ8xh5G3BJCMt4WXe1H10UNaVOamqQTmrx1kjVuxAHIQSNU6hY4Nsn9/ag==", + "dev": true }, - "node_modules/detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", + "node_modules/cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", "dev": true, "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" } }, - "node_modules/detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==", "dev": true, "engines": { "node": ">=0.10.0" } }, - "node_modules/devtools": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", - "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", + "node_modules/collection-map": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-map/-/collection-map-1.0.0.tgz", + "integrity": "sha512-5D2XXSpkOnleOI21TG7p3T0bGAsZ/XknZpKBmGYyluO8pw4zA3K8ZlrBIbC4FXg3m6z/RNFiUFfT2sQK01+UHA==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^9.0.0" + "arr-map": "^2.0.2", + "for-own": "^1.0.0", + "make-iterator": "^1.0.0" }, "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/devtools-protocol": { - "version": "0.0.1260888", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", - "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", - "dev": true - }, - "node_modules/devtools/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "node": ">=0.10.0" } }, - "node_modules/devtools/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/devtools/node_modules/@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "color-name": "1.1.3" } }, - "node_modules/devtools/node_modules/@types/which": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/which/-/which-1.3.2.tgz", - "integrity": "sha512-8oDqyLC7eD4HM307boe2QWKyuzdzWBj56xI/imSl2cpL+U3tCMaTAkMJ4ee5JBZ/FsOJlvRGeIShiZDAl1qERA==", + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true }, - "node_modules/devtools/node_modules/@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "dev": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "dependencies": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" - }, "engines": { - "node": ">=12.0.0" + "node": ">=0.1.90" } }, - "node_modules/devtools/node_modules/@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, "dependencies": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "delayed-stream": "~1.0.0" }, "engines": { - "node": ">=12.0.0" + "node": ">= 0.8" } }, - "node_modules/devtools/node_modules/@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", "dev": true, - "engines": { - "node": ">=12.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devtools/node_modules/@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/comment-parser": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, - "dependencies": { - "@types/node": "^18.0.0", - "got": "^11.8.1" - }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "^4.6.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 12.0.0" } }, - "node_modules/devtools/node_modules/@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/component-emitter": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.1.tgz", + "integrity": "sha512-T0+barUSQRTUQASh8bx02dl+DhF54GtIDY13Y3m9oWTklKbb3Wv974meRpeZ3lp1JpLVECWWNHC4vaG2XHXouQ==", "dev": true, - "dependencies": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" - }, - "engines": { - "node": ">=12.0.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devtools/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/compress-commons": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", + "normalize-path": "^3.0.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">= 14" } }, - "node_modules/devtools/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/compress-commons/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/devtools/node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true, - "engines": { - "node": ">=10.6.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/devtools/node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "node_modules/compress-commons/node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, "engines": { "node": ">=8" - } - }, - "node_modules/devtools/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" }, "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devtools/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/compress-commons/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=7.0.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/devtools/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/devtools/node_modules/debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "node_modules/compress-commons/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "safe-buffer": "~5.2.0" } }, - "node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.948846", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", - "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, - "node_modules/devtools/node_modules/edge-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-2.2.1.tgz", - "integrity": "sha512-AI5fC7dfDmCdKo3m5y7PkYE8m6bMqR6pvVpgtrZkkhcJXFLelUgkjrhk3kXXx8Kbw2cRaTT4LkOR7hqf39KJdw==", + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", "dev": true, + "engines": [ + "node >= 0.8" + ], "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/devtools/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/concat-with-sourcemaps": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz", + "integrity": "sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==", "dev": true, "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "source-map": "^0.6.1" } }, - "node_modules/devtools/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/concat-with-sourcemaps/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.10.0" } }, - "node_modules/devtools/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "node_modules/connect": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", + "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", "dev": true, "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" }, "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "node": ">= 0.10.0" } }, - "node_modules/devtools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/connect-livereload": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/connect-livereload/-/connect-livereload-0.6.1.tgz", + "integrity": "sha512-3R0kMOdL7CjJpU66fzAkCe6HNtd3AavCS4m+uW4KtJjrdGPT0SQEZieAYd+cm+lJoBznNQ4lqipYWkhBMgk00g==", "dev": true, "engines": { - "node": ">=8" + "node": "*" } }, - "node_modules/devtools/node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, - "engines": { - "node": ">=10.19.0" + "ms": "2.0.0" } }, - "node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "node_modules/connect/node_modules/finalhandler": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", + "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", "dev": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">= 6" + "node": ">= 0.8" } }, - "node_modules/devtools/node_modules/isexe": { + "node_modules/connect/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "dev": true }, - "node_modules/devtools/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "node_modules/connect/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", "dev": true, + "dependencies": { + "ee-first": "1.1.1" + }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, - "node_modules/devtools/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/connect/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/consolidate": { + "version": "0.15.1", + "resolved": "https://registry.npmjs.org/consolidate/-/consolidate-0.15.1.tgz", + "integrity": "sha512-DW46nrsMJgy9kqAbPt5rKaCr7uFtpo4mSUvLHIUbJEjm0vo+aY5QLwBUq3FK4tRnJr/X0Psc0C4jf/h+HtXSMw==", + "deprecated": "Please upgrade to consolidate v1.0.0+ as it has been modernized with several long-awaited fixes implemented. Maintenance is supported by Forward Email at https://forwardemail.net ; follow/watch https://github.com/ladjs/consolidate for updates and release changelog", "dependencies": { - "brace-expansion": "^2.0.1" + "bluebird": "^3.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.10.0" } }, - "node_modules/devtools/node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", "dependencies": { - "whatwg-url": "^5.0.0" + "safe-buffer": "5.2.1" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 0.6" } }, - "node_modules/devtools/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "dev": true, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/devtools/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true, + "node_modules/continuable-cache": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/continuable-cache/-/continuable-cache-0.3.1.tgz", + "integrity": "sha512-TF30kpKhTH8AGCG3dut0rdd/19B7Z+qCnrMoBLpyQu/2drZdNrrpcjPEoJeSVsQM+8KmWG5O56oPDjSSUsuTyA==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cookie": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==", "engines": { - "node": ">=8" + "node": ">= 0.6" } }, - "node_modules/devtools/node_modules/puppeteer-core": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", - "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, + "node_modules/copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==", "dev": true, - "dependencies": { - "debug": "4.3.2", - "devtools-protocol": "0.0.948846", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.7", - "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": "8.2.3" - }, "engines": { - "node": ">=10.18.1" + "node": ">=0.10.0" } }, - "node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/copy-props": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" } }, - "node_modules/devtools/node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" - }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "hasInstallScript": true, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/devtools/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, + "node_modules/core-js-compat": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", + "integrity": "sha512-9TNiImhKvQqSUkOvk/mMRZzOANTiEVC7WaBNhHcKM7x+/5E1l5NvsysR19zuDQScE8k+kfQXWRN3AtS/eOSHpg==", "dependencies": { - "ansi-regex": "^5.0.1" + "browserslist": "^4.23.0" }, - "engines": { - "node": ">=8" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/devtools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "node_modules/core-js-pure": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js-pure/-/core-js-pure-3.37.1.tgz", + "integrity": "sha512-J/r5JTHSmzTxbiYYrzXg9w1VpqrYt+gexenBE9pugeyhwPZTAEJddyiReJWsLO6uNQ8xJZFbod6XC7KKwatCiA==", + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" } }, - "node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, - "node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", "dev": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=6" - } - }, - "node_modules/devtools/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "engines": { - "node": "*" + "node": ">= 0.10" } }, - "node_modules/devtools/node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/coveralls": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.1.1.tgz", + "integrity": "sha512-+dxnG2NHncSD1NrqbSM3dn/lE57O6Qf/koe9+I7c+wzkqRmEvcp0kgJdxKInzYzkICKkFMZsX3Vct3++tsF9ww==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "js-yaml": "^3.13.1", + "lcov-parse": "^1.0.0", + "log-driver": "^1.2.7", + "minimist": "^1.2.5", + "request": "^2.88.2" }, "bin": { - "node-which": "bin/node-which" + "coveralls": "bin/coveralls.js" }, "engines": { - "node": ">= 8" + "node": ">=6" } }, - "node_modules/devtools/node_modules/ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", + "node_modules/crc-32": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", + "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "bin": { + "crc32": "bin/crc32.njs" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", - "dev": true - }, - "node_modules/diff": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", - "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=0.8" } }, - "node_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/crc32-stream": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "crc-32": "^1.2.0", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">=6.0.0" + "node": ">= 14" } }, - "node_modules/doctrine-temporary-fork": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", - "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", + "node_modules/crc32-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "node_modules/documentation": { - "version": "14.0.3", - "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", - "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", - "dev": true, - "dependencies": { - "@babel/core": "^7.18.10", - "@babel/generator": "^7.18.10", - "@babel/parser": "^7.18.11", - "@babel/traverse": "^7.18.11", - "@babel/types": "^7.18.10", - "chalk": "^5.0.1", - "chokidar": "^3.5.3", - "diff": "^5.1.0", - "doctrine-temporary-fork": "2.1.0", - "git-url-parse": "^13.1.0", - "github-slugger": "1.4.0", - "glob": "^8.0.3", - "globals-docs": "^2.4.1", - "highlight.js": "^11.6.0", - "ini": "^3.0.0", - "js-yaml": "^4.1.0", - "konan": "^2.1.1", - "lodash": "^4.17.21", - "mdast-util-find-and-replace": "^2.2.1", - "mdast-util-inject": "^1.1.0", - "micromark-util-character": "^1.1.0", - "parse-filepath": "^1.0.2", - "pify": "^6.0.0", - "read-pkg-up": "^9.1.0", - "remark": "^14.0.2", - "remark-gfm": "^3.0.1", - "remark-html": "^15.0.1", - "remark-reference-links": "^6.0.1", - "remark-toc": "^8.0.1", - "resolve": "^1.22.1", - "strip-json-comments": "^5.0.0", - "unist-builder": "^3.0.0", - "unist-util-visit": "^4.1.0", - "vfile": "^5.3.4", - "vfile-reporter": "^7.0.4", - "vfile-sort": "^3.0.0", - "yargs": "^17.5.1" - }, - "bin": { - "documentation": "bin/documentation.js" + "node_modules/crc32-stream/node_modules/readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "dependencies": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@vue/compiler-sfc": "^3.2.37", - "vue-template-compiler": "^2.7.8" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/documentation/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/documentation/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/crc32-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "safe-buffer": "~5.2.0" } }, - "node_modules/documentation/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "extraneous": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" + "node": "4.x || >=6.0.0" }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } } }, - "node_modules/documentation/node_modules/find-up": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", - "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, "dependencies": { - "locate-path": "^7.1.0", - "path-exists": "^5.0.0" + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/documentation/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/cross-spawn/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/cross-spawn/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "isexe": "^2.0.0" }, - "engines": { - "node": ">=12" + "bin": { + "node-which": "bin/node-which" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">= 8" } }, - "node_modules/documentation/node_modules/hosted-git-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", - "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==" + }, + "node_modules/css": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/css/-/css-3.0.0.tgz", + "integrity": "sha512-DG9pFfwOrzc+hawpmqX/dHYHJG+Bsdb0klhyi1sDneOgGOXy9wQIC8hzyVp1e4NRYDBdxcylvywPkkXCHAzTyQ==", "dev": true, "dependencies": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" + "inherits": "^2.0.4", + "source-map": "^0.6.1", + "source-map-resolve": "^0.6.0" } }, - "node_modules/documentation/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/css-select": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/css-select/-/css-select-5.1.0.tgz", + "integrity": "sha512-nwoRF1rvRRnnCqqY7updORDsuqKzqYJ28+oSMaJMMgOauh3fvwHqMS7EZpIPqK8GL+g9mKxF1vP/ZjSeNjEVHg==", "dev": true, "dependencies": { - "argparse": "^2.0.1" + "boolbase": "^1.0.0", + "css-what": "^6.1.0", + "domhandler": "^5.0.2", + "domutils": "^3.0.1", + "nth-check": "^2.0.1" }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "funding": { + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/documentation/node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "node_modules/css-shorthand-properties": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/css-shorthand-properties/-/css-shorthand-properties-1.1.1.tgz", + "integrity": "sha512-Md+Juc7M3uOdbAFwOYlTrccIZ7oCFuzrhKYQjdeUEW/sE1hv17Jp/Bws+ReOPpGVBTYCBoYo+G17V5Qo8QQ75A==", "dev": true }, - "node_modules/documentation/node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "node_modules/css-value": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/css-value/-/css-value-0.0.1.tgz", + "integrity": "sha512-FUV3xaJ63buRLgHrLQVlVgQnQdR4yqdLGaDu7g8CQcWjInDfM9plBTPI9FRfpahju1UBSaMckeb2/46ApS/V1Q==", "dev": true }, - "node_modules/documentation/node_modules/locate-path": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", - "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", + "node_modules/css-what": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==", "dev": true, - "dependencies": { - "p-locate": "^6.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 6" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/fb55" } }, - "node_modules/documentation/node_modules/lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "node_modules/css/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "yallist": "^4.0.0" - }, "engines": { - "node": ">=10" + "node": ">=0.10.0" } }, - "node_modules/documentation/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/csv-writer": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/csv-writer/-/csv-writer-1.6.0.tgz", + "integrity": "sha512-NOx7YDFWEsM/fTRAJjRpPp8t+MKRVvniAg9wQlUKx20MFrPs73WLJhFf5iteqrxNYnsy924K3Iroh3yNHeYd2g==", + "dev": true + }, + "node_modules/custom-event": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", + "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==", + "dev": true + }, + "node_modules/d": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz", + "integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "es5-ext": "^0.10.64", + "type": "^2.7.2" }, "engines": { - "node": ">=10" + "node": ">=0.12" } }, - "node_modules/documentation/node_modules/normalize-package-data": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", - "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", + "node_modules/dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", "dev": true, "dependencies": { - "hosted-git-info": "^4.0.1", - "is-core-module": "^2.5.0", - "semver": "^7.3.4", - "validate-npm-package-license": "^3.0.1" + "assert-plus": "^1.0.0" }, "engines": { - "node": ">=10" + "node": ">=0.10" } }, - "node_modules/documentation/node_modules/p-limit": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", - "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", + "node_modules/data-uri-to-buffer": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz", + "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==", "dev": true, - "dependencies": { - "yocto-queue": "^1.0.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 12" } }, - "node_modules/documentation/node_modules/p-locate": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", - "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", + "node_modules/data-view-buffer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.1.tgz", + "integrity": "sha512-0lht7OugA5x3iJLOWFhWK/5ehONdprk0ISXqVFn/NFrDu+cuc8iADFrGQz5BnRK7LLU3JmkbXSxaqX+/mXYtUA==", "dev": true, "dependencies": { - "p-limit": "^4.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/documentation/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "node_modules/data-view-byte-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.1.tgz", + "integrity": "sha512-4J7wRJD3ABAzr8wP+OcIcqq2dlUKp4DVflx++hs5h5ZKydWMI6/D/fAot+yh6g2tHh8fLFTvNOaVN357NvSrOQ==", "dev": true, "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=8" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/documentation/node_modules/path-exists": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", - "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/documentation/node_modules/read-pkg": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", - "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "node_modules/data-view-byte-offset": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.0.tgz", + "integrity": "sha512-t/Ygsytq+R995EJ5PZlD4Cu56sWa8InXySaViRzw9apusqsOO2bQP+SbYzAhR0pFKoB+43lYy8rWban9JSuXnA==", "dev": true, "dependencies": { - "@types/normalize-package-data": "^2.4.1", - "normalize-package-data": "^3.0.2", - "parse-json": "^5.2.0", - "type-fest": "^2.0.0" + "call-bind": "^1.0.6", + "es-errors": "^1.3.0", + "is-data-view": "^1.0.1" }, "engines": { - "node": ">=12.20" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/documentation/node_modules/read-pkg-up": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", - "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", + "node_modules/date-format": { + "version": "4.0.14", + "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.14.tgz", + "integrity": "sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==", "dev": true, - "dependencies": { - "find-up": "^6.3.0", - "read-pkg": "^7.1.0", - "type-fest": "^2.5.0" - }, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "node_modules/documentation/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/dateformat": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", + "integrity": "sha512-GODcnWq3YGoTnygPfi02ygEiRxqUxpJwuRHjdhJYuxpcZmDq4rjBiXYmbCCzStxo176ixfLT6i4NPwQooRySnw==", "dev": true, - "bin": { - "semver": "bin/semver.js" - }, "engines": { - "node": ">=10" + "node": "*" } }, - "node_modules/documentation/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", "dev": true, - "engines": { - "node": ">=12.20" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "optional": true }, - "node_modules/documentation/node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "node_modules/debounce": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/debounce/-/debounce-1.2.1.tgz", + "integrity": "sha512-XRRe6Glud4rd/ZGQfiV1ruXSfbvfJedlV9Y6zOlP+2K04vBYiJEte6stfFkCP03aMnY5tsipamumUjL14fofug==", "dev": true }, - "node_modules/documentation/node_modules/yargs": { - "version": "17.7.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", - "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", - "dev": true, + "node_modules/debug": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "ms": "^2.1.3" }, "engines": { - "node": ">=12" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", + "node_modules/debug-fabulous": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/debug-fabulous/-/debug-fabulous-1.1.0.tgz", + "integrity": "sha512-GZqvGIgKNlUnHUPQhepnUZFIMoi3dgZKQBzKDeL2g7oJF9SNAji/AAu36dusFUas0O+pae74lNeoIPHqXWDkLg==", "dev": true, "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "debug": "3.X", + "memoizee": "0.4.X", + "object-assign": "4.X" } }, - "node_modules/dom-serializer": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", - "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "node_modules/debug-fabulous/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "domelementtype": "^2.3.0", - "domhandler": "^5.0.2", - "entities": "^4.2.0" - }, - "funding": { - "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + "ms": "^2.1.1" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", - "dev": true - }, - "node_modules/domelementtype": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", - "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/fb55" - } - ] + "node_modules/debug/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node_modules/domhandler": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", - "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "node_modules/decamelize": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-6.0.0.tgz", + "integrity": "sha512-Fv96DCsdOgB6mdGl67MT5JaTNKRzrzill5OH5s8bjYJXVlcXyPYGyPsUkWyGV5p1TXI5esYIYMMeDJL0hEIwaA==", "dev": true, - "dependencies": { - "domelementtype": "^2.3.0" - }, "engines": { - "node": ">= 4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/fb55/domhandler?sponsor=1" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/domutils": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", - "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", + "node_modules/decode-named-character-reference": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/decode-named-character-reference/-/decode-named-character-reference-1.0.2.tgz", + "integrity": "sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==", "dev": true, "dependencies": { - "dom-serializer": "^2.0.0", - "domelementtype": "^2.3.0", - "domhandler": "^5.0.3" + "character-entities": "^2.0.0" }, "funding": { - "url": "https://github.com/fb55/domutils?sponsor=1" + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/dotenv": { - "version": "16.4.5", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", - "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dset": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", - "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==", - "engines": { - "node": ">=4" - } - }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", - "dev": true, - "dependencies": { - "readable-stream": "~1.1.9" - } - }, - "node_modules/duplexer2/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/duplexer2/node_modules/readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "node": ">=0.10" } }, - "node_modules/duplexer2/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", - "dev": true - }, - "node_modules/duplexify": { + "node_modules/deep-eql": { "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", "dev": true, "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/duplexify/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" }, "engines": { - "node": ">= 6" + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/each-props": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", - "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true + }, + "node_modules/deepmerge-ts": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", + "integrity": "sha512-eS8dRJOckyo9maw9Tu5O5RUi/4inFLrnoLkBe3cPfDMx3WZioXtmOew4TXQaxq7Rhl4xjDtR7c6x8nNTxOvbFw==", "dev": true, - "dependencies": { - "is-plain-object": "^2.0.1", - "object.defaults": "^1.1.0" + "engines": { + "node": ">=16.0.0" } }, - "node_modules/each-props/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/default-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/default-compare/-/default-compare-1.0.0.tgz", + "integrity": "sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "kind-of": "^5.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/eastasianwidth": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", - "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", - "dev": true + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/easy-table": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", - "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", + "node_modules/default-resolution": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", + "integrity": "sha512-2xaP6GiwVwOEbXCGoJ4ufgC76m8cj805jrghScewJC2ZDsb9U0b4BIrba+xt/Uytyd0HvQ6+WymSRTfnYj59GQ==", "dev": true, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dev": true, + "optional": true, "dependencies": { - "ansi-regex": "^5.0.1" + "clone": "^1.0.2" }, - "optionalDependencies": { - "wcwidth": "^1.0.1" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "node_modules/defaults/node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, - "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "optional": true, + "engines": { + "node": ">=0.8" } }, - "node_modules/ecc-jsbn/node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", - "dev": true + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "node_modules/edge-paths": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", - "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "dev": true, "dependencies": { - "@types/which": "^2.0.1", - "which": "^2.0.2" + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" }, "engines": { - "node": ">=14.0.0" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/shirshak55" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/edge-paths/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "node_modules/edge-paths/node_modules/which": { + "node_modules/define-property": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" }, "engines": { - "node": ">= 8" + "node": ">=0.10.0" } }, - "node_modules/edgedriver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.5.0.tgz", - "integrity": "sha512-qQIjDQ62cbDcoZ8AcN6PxQekOwGDZcTkdZj5qr6Ew1i4mMi3R0d1Y6DKlyUnkBs5GXUYua5wKB0XHMLj6FAChQ==", + "node_modules/degenerator": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", + "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", "dev": true, - "hasInstallScript": true, "dependencies": { - "@wdio/logger": "^8.28.0", - "decamelize": "^6.0.0", - "edge-paths": "^3.0.5", - "node-fetch": "^3.3.2", - "unzipper": "^0.11.6", - "which": "^4.0.0" + "ast-types": "^0.13.4", + "escodegen": "^2.1.0", + "esprima": "^4.0.1" }, - "bin": { - "edgedriver": "bin/edgedriver.js" + "engines": { + "node": ">= 14" } }, - "node_modules/ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" - }, - "node_modules/ejs": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", - "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", + "node_modules/degenerator/node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", "dev": true, "dependencies": { - "jake": "^10.8.5" + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" }, "bin": { - "ejs": "bin/cli.js" + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" } }, - "node_modules/electron-to-chromium": { - "version": "1.4.774", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.774.tgz", - "integrity": "sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "node_modules/emojis-list": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", - "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "node_modules/degenerator/node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, "engines": { - "node": ">= 4" + "node": ">=4" } }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "node_modules/degenerator/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, "engines": { - "node": ">= 0.8" + "node": ">=4.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/degenerator/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "dependencies": { - "once": "^1.4.0" + "optional": true, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/engine.io": { - "version": "6.5.4", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", - "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" - }, "engines": { - "node": ">=10.2.0" + "node": ">=0.4.0" } }, - "node_modules/engine.io-parser": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", - "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", - "dev": true, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "engines": { - "node": ">=10.0.0" + "node": ">= 0.8" } }, - "node_modules/engine.io/node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=6" } }, - "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.2.4", - "tapable": "^2.2.0" - }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", "engines": { - "node": ">=10.13.0" + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" } }, - "node_modules/enquirer": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", - "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", + "node_modules/detect-file": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-1.0.0.tgz", + "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true, - "dependencies": { - "ansi-colors": "^4.1.1", - "strip-ansi": "^6.0.1" - }, "engines": { - "node": ">=8.6" + "node": ">=0.10.0" } }, - "node_modules/enquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/detect-newline": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", + "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", + "node_modules/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "dev": true }, - "node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "node_modules/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "dev": true, "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node": ">=0.3.1" } }, - "node_modules/errno": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", - "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", "dev": true, - "dependencies": { - "prr": "~1.0.1" - }, - "bin": { - "errno": "cli.js" + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "node_modules/error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "string-template": "~0.2.1" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" } }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "node_modules/doctrine-temporary-fork": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", + "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", "dev": true, "dependencies": { - "is-arrayish": "^0.2.1" + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/es-abstract": { - "version": "1.23.3", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", - "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", + "node_modules/documentation": { + "version": "14.0.3", + "resolved": "https://registry.npmjs.org/documentation/-/documentation-14.0.3.tgz", + "integrity": "sha512-B7cAviVKN9Rw7Ofd+9grhVuxiHwly6Ieh+d/ceMw8UdBOv/irkuwnDEJP8tq0wgdLJDUVuIkovV+AX9mTrZFxg==", "dev": true, "dependencies": { - "array-buffer-byte-length": "^1.0.1", - "arraybuffer.prototype.slice": "^1.0.3", - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.7", - "data-view-buffer": "^1.0.1", - "data-view-byte-length": "^1.0.1", - "data-view-byte-offset": "^1.0.0", - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.0.0", - "es-set-tostringtag": "^2.0.3", - "es-to-primitive": "^1.2.1", - "function.prototype.name": "^1.1.6", - "get-intrinsic": "^1.2.4", - "get-symbol-description": "^1.0.2", - "globalthis": "^1.0.3", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2", - "has-proto": "^1.0.3", - "has-symbols": "^1.0.3", - "hasown": "^2.0.2", - "internal-slot": "^1.0.7", - "is-array-buffer": "^3.0.4", - "is-callable": "^1.2.7", - "is-data-view": "^1.0.1", - "is-negative-zero": "^2.0.3", - "is-regex": "^1.1.4", - "is-shared-array-buffer": "^1.0.3", - "is-string": "^1.0.7", - "is-typed-array": "^1.1.13", - "is-weakref": "^1.0.2", - "object-inspect": "^1.13.1", - "object-keys": "^1.1.1", - "object.assign": "^4.1.5", - "regexp.prototype.flags": "^1.5.2", - "safe-array-concat": "^1.1.2", - "safe-regex-test": "^1.0.3", - "string.prototype.trim": "^1.2.9", - "string.prototype.trimend": "^1.0.8", - "string.prototype.trimstart": "^1.0.8", - "typed-array-buffer": "^1.0.2", - "typed-array-byte-length": "^1.0.1", - "typed-array-byte-offset": "^1.0.2", - "typed-array-length": "^1.0.6", - "unbox-primitive": "^1.0.2", - "which-typed-array": "^1.1.15" + "@babel/core": "^7.18.10", + "@babel/generator": "^7.18.10", + "@babel/parser": "^7.18.11", + "@babel/traverse": "^7.18.11", + "@babel/types": "^7.18.10", + "chalk": "^5.0.1", + "chokidar": "^3.5.3", + "diff": "^5.1.0", + "doctrine-temporary-fork": "2.1.0", + "git-url-parse": "^13.1.0", + "github-slugger": "1.4.0", + "glob": "^8.0.3", + "globals-docs": "^2.4.1", + "highlight.js": "^11.6.0", + "ini": "^3.0.0", + "js-yaml": "^4.1.0", + "konan": "^2.1.1", + "lodash": "^4.17.21", + "mdast-util-find-and-replace": "^2.2.1", + "mdast-util-inject": "^1.1.0", + "micromark-util-character": "^1.1.0", + "parse-filepath": "^1.0.2", + "pify": "^6.0.0", + "read-pkg-up": "^9.1.0", + "remark": "^14.0.2", + "remark-gfm": "^3.0.1", + "remark-html": "^15.0.1", + "remark-reference-links": "^6.0.1", + "remark-toc": "^8.0.1", + "resolve": "^1.22.1", + "strip-json-comments": "^5.0.0", + "unist-builder": "^3.0.0", + "unist-util-visit": "^4.1.0", + "vfile": "^5.3.4", + "vfile-reporter": "^7.0.4", + "vfile-sort": "^3.0.0", + "yargs": "^17.5.1" + }, + "bin": { + "documentation": "bin/documentation.js" }, "engines": { - "node": ">= 0.4" + "node": ">=14" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "optionalDependencies": { + "@vue/compiler-sfc": "^3.2.37", + "vue-template-compiler": "^2.7.8" } }, - "node_modules/es-define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "node_modules/documentation/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "node_modules/documentation/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4" - }, - "engines": { - "node": ">= 0.4" + "balanced-match": "^1.0.0" } }, - "node_modules/es-errors": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "node_modules/documentation/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, "engines": { - "node": ">= 0.4" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/es-get-iterator": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", - "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "node_modules/documentation/node_modules/find-up": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-6.3.0.tgz", + "integrity": "sha512-v2ZsoEuVHYy8ZIlYqwPe/39Cy+cFDzp4dXPaxNvkEuouymu+2Jbz0PxpKarJHYJTmv2HWT3O382qY8l4jMWthw==", "dev": true, "dependencies": { - "call-bind": "^1.0.2", - "get-intrinsic": "^1.1.3", - "has-symbols": "^1.0.3", - "is-arguments": "^1.1.1", - "is-map": "^2.0.2", - "is-set": "^2.0.2", - "is-string": "^1.0.7", - "isarray": "^2.0.5", - "stop-iteration-iterator": "^1.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es-module-lexer": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", - "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", - "dev": true - }, - "node_modules/es-object-atoms": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", - "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", + "node_modules/documentation/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "dev": true, "dependencies": { - "es-errors": "^1.3.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">= 0.4" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/es-set-tostringtag": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", - "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", + "node_modules/documentation/node_modules/hosted-git-info": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz", + "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==", "dev": true, "dependencies": { - "get-intrinsic": "^1.2.4", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.1" + "lru-cache": "^6.0.0" }, "engines": { - "node": ">= 0.4" + "node": ">=10" } }, - "node_modules/es-shim-unscopables": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", - "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", + "node_modules/documentation/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "dev": true, "dependencies": { - "hasown": "^2.0.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/es-to-primitive": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", - "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "node_modules/documentation/node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/documentation/node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/documentation/node_modules/locate-path": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-7.2.0.tgz", + "integrity": "sha512-gvVijfZvn7R+2qyPX8mAuKcFGDf6Nc61GdvGafQsHL0sBIxfKzA+usWn4GFC/bk+QdwPUD4kWFJLhElipq+0VA==", "dev": true, "dependencies": { - "is-callable": "^1.1.4", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.2" + "p-locate": "^6.0.0" }, "engines": { - "node": ">= 0.4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es5-ext": { - "version": "0.10.64", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", - "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", + "node_modules/documentation/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "dev": true, - "hasInstallScript": true, "dependencies": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "esniff": "^2.0.1", - "next-tick": "^1.1.0" + "yallist": "^4.0.0" }, "engines": { - "node": ">=0.10" + "node": ">=10" } }, - "node_modules/es5-shim": { - "version": "4.6.7", - "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", - "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", + "node_modules/documentation/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=0.4.0" + "node": ">=10" } }, - "node_modules/es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", + "node_modules/documentation/node_modules/normalize-package-data": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz", + "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==", "dev": true, "dependencies": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" + "hosted-git-info": "^4.0.1", + "is-core-module": "^2.5.0", + "semver": "^7.3.4", + "validate-npm-package-license": "^3.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", + "node_modules/documentation/node_modules/p-limit": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-4.0.0.tgz", + "integrity": "sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==", "dev": true, "dependencies": { - "es6-promise": "^4.0.3" + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es6-symbol": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", - "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", + "node_modules/documentation/node_modules/p-locate": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-6.0.0.tgz", + "integrity": "sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==", "dev": true, "dependencies": { - "d": "^1.0.2", - "ext": "^1.7.0" + "p-limit": "^4.0.0" }, "engines": { - "node": ">=0.12" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/es6-weak-map": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", - "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "node_modules/documentation/node_modules/parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", "dev": true, "dependencies": { - "d": "1", - "es5-ext": "^0.10.46", - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.1" + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "node_modules/documentation/node_modules/path-exists": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-5.0.0.tgz", + "integrity": "sha512-RjhtfwJOxzcFmNOi6ltcbcu4Iu+FL3zEj83dk4kAS+fVpTxXLO1b38RvJgT/0QwvV/L3aY9TAnyv0EOqW4GoMQ==", + "dev": true, "engines": { - "node": ">=6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "node_modules/documentation/node_modules/read-pkg": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-7.1.0.tgz", + "integrity": "sha512-5iOehe+WF75IccPc30bWTbpdDQLOCc3Uu8bi3Dte3Eueij81yx1Mrufk8qBx/YAbR4uL1FdUr+7BKXDwEtisXg==", + "dev": true, + "dependencies": { + "@types/normalize-package-data": "^2.4.1", + "normalize-package-data": "^3.0.2", + "parse-json": "^5.2.0", + "type-fest": "^2.0.0" + }, "engines": { - "node": ">=0.8.0" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", - "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", + "node_modules/documentation/node_modules/read-pkg-up": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-9.1.0.tgz", + "integrity": "sha512-vaMRR1AC1nrd5CQM0PhlRsO5oc2AAigqr7cCrZ/MW/Rsaflz4RlgzkpL4qoU/z1F6wrbd85iFv1OQj/y5RdGvg==", "dev": true, "dependencies": { - "esprima": "^2.7.1", - "estraverse": "^1.9.1", - "esutils": "^2.0.2", - "optionator": "^0.8.1" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" + "find-up": "^6.3.0", + "read-pkg": "^7.1.0", + "type-fest": "^2.5.0" }, "engines": { - "node": ">=0.12.0" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, - "optionalDependencies": { - "source-map": "~0.2.0" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen/node_modules/estraverse": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", - "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "node_modules/documentation/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/escodegen/node_modules/levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "node_modules/documentation/node_modules/type-fest": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, - "dependencies": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - }, "engines": { - "node": ">= 0.8.0" + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen/node_modules/optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "node_modules/documentation/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/documentation/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">= 0.8.0" + "node": ">=12" } }, - "node_modules/escodegen/node_modules/prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "node_modules/dom-serialize": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", + "integrity": "sha512-Yra4DbvoW7/Z6LBN560ZwXMjoNOSAN2wRsKFGc4iBeso+mpIA6qj1vfdf9HpMaKAqG6wXTy+1SYEzmNpKXOSsQ==", "dev": true, - "engines": { - "node": ">= 0.8.0" + "dependencies": { + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", - "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", "dev": true, - "optional": true, "dependencies": { - "amdefine": ">=0.0.4" + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" }, - "engines": { - "node": ">=0.8.0" + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" } }, - "node_modules/escodegen/node_modules/type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==", + "dev": true + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ] + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", "dev": true, "dependencies": { - "prelude-ls": "~1.1.2" + "domelementtype": "^2.3.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" } }, - "node_modules/eslint": { - "version": "7.32.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", - "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", + "node_modules/domutils": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.1.0.tgz", + "integrity": "sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA==", "dev": true, "dependencies": { - "@babel/code-frame": "7.12.11", - "@eslint/eslintrc": "^0.4.3", - "@humanwhocodes/config-array": "^0.5.0", - "ajv": "^6.10.0", - "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", - "debug": "^4.0.1", - "doctrine": "^3.0.0", - "enquirer": "^2.3.5", - "escape-string-regexp": "^4.0.0", - "eslint-scope": "^5.1.1", - "eslint-utils": "^2.1.0", - "eslint-visitor-keys": "^2.0.0", - "espree": "^7.3.1", - "esquery": "^1.4.0", - "esutils": "^2.0.2", - "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", - "functional-red-black-tree": "^1.0.1", - "glob-parent": "^5.1.2", - "globals": "^13.6.0", - "ignore": "^4.0.6", - "import-fresh": "^3.0.0", - "imurmurhash": "^0.1.4", - "is-glob": "^4.0.0", - "js-yaml": "^3.13.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", - "lodash.merge": "^4.6.2", - "minimatch": "^3.0.4", - "natural-compare": "^1.4.0", - "optionator": "^0.9.1", - "progress": "^2.0.0", - "regexpp": "^3.1.0", - "semver": "^7.2.1", - "strip-ansi": "^6.0.0", - "strip-json-comments": "^3.1.0", - "table": "^6.0.9", - "text-table": "^0.2.0", - "v8-compile-cache": "^2.0.3" - }, - "bin": { - "eslint": "bin/eslint.js" + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=12" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://dotenvx.com" + } + }, + "node_modules/dset": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==", + "engines": { + "node": ">=4" } }, - "node_modules/eslint-config-standard": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", - "integrity": "sha512-UkFojTV1o0GOe1edOEiuI5ccYLJSuNngtqSeClNzhsmG8KPJ+7mRxgtp2oYhqZAK/brlXMoCd+VgXViE0AfyKw==", + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/duplexer2": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", + "integrity": "sha512-+AWBwjGadtksxjOQSFDhPNQbed7icNXApT4+2BNpsXzcCBiInq2H9XW0O8sfHFaPmnQRs7cg/P0fAr2IWQSW0g==", "dev": true, - "peerDependencies": { - "eslint": ">=3.19.0", - "eslint-plugin-import": ">=2.2.0", - "eslint-plugin-node": ">=4.2.2", - "eslint-plugin-promise": ">=3.5.0", - "eslint-plugin-standard": ">=3.0.0" + "dependencies": { + "readable-stream": "~1.1.9" } }, - "node_modules/eslint-import-resolver-node": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", - "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", + "node_modules/duplexer2/node_modules/isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "node_modules/duplexer2/node_modules/readable-stream": { + "version": "1.1.14", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", + "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", "dev": true, "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "node_modules/eslint-import-resolver-node/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/duplexer2/node_modules/string_decoder": { + "version": "0.10.31", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", + "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", + "dev": true + }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" } }, - "node_modules/eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "dev": true, "dependencies": { - "debug": "^3.2.7" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } + "node": ">= 6" } }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/each-props": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/each-props/-/each-props-1.3.2.tgz", + "integrity": "sha512-vV0Hem3zAGkJAyU7JSjixeU66rwdynTAa1vofCrSA5fEln+m67Az9CcnkVD776/fsN/UjIWmBDoNRS6t6G9RfA==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "is-plain-object": "^2.0.1", + "object.defaults": "^1.1.0" } }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", + "node_modules/each-props/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" + "isobject": "^3.0.1" }, "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" + "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", + "dev": true + }, + "node_modules/easy-table": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/easy-table/-/easy-table-1.2.0.tgz", + "integrity": "sha512-OFzVOv03YpvtcWGe5AayU5G2hgybsg3iqA6drU8UaoZyB9jLGMTrz9+asnLp/E+6qPh88yEI1gvyZFZ41dmgww==", "dev": true, "dependencies": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", - "array.prototype.flat": "^1.3.2", - "array.prototype.flatmap": "^1.3.2", - "debug": "^3.2.7", - "doctrine": "^2.1.0", - "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", - "is-glob": "^4.0.3", - "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", - "semver": "^6.3.1", - "tsconfig-paths": "^3.15.0" - }, - "engines": { - "node": ">=4" + "ansi-regex": "^5.0.1" }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" + "optionalDependencies": { + "wcwidth": "^1.0.1" } }, - "node_modules/eslint-plugin-import/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "node_modules/ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", "dev": true, "dependencies": { - "ms": "^2.1.1" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "node_modules/ecc-jsbn/node_modules/jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==", + "dev": true + }, + "node_modules/edge-paths": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/edge-paths/-/edge-paths-3.0.5.tgz", + "integrity": "sha512-sB7vSrDnFa4ezWQk9nZ/n0FdpdUuC6R1EOrlU3DL+bovcNFK28rqu2emmAUjujYEJTWIgQGqgVVWUZXMnc8iWg==", "dev": true, "dependencies": { - "esutils": "^2.0.2" + "@types/which": "^2.0.1", + "which": "^2.0.2" }, "engines": { - "node": ">=0.10.0" + "node": ">=14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/shirshak55" } }, - "node_modules/eslint-plugin-jsdoc": { - "version": "38.1.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.1.6.tgz", - "integrity": "sha512-n4s95oYlg0L43Bs8C0dkzIldxYf8pLCutC/tCbjIdF7VDiobuzPI+HZn9Q0BvgOvgPNgh5n7CSStql25HUG4Tw==", + "node_modules/edge-paths/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/edge-paths/node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, "dependencies": { - "@es-joy/jsdoccomment": "~0.22.1", - "comment-parser": "1.3.1", - "debug": "^4.3.4", - "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "regextras": "^0.8.0", - "semver": "^7.3.5", - "spdx-expression-parse": "^3.0.1" + "isexe": "^2.0.0" }, - "engines": { - "node": "^12 || ^14 || ^16 || ^17" + "bin": { + "node-which": "bin/node-which" }, - "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" - } - }, - "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 8" } }, - "node_modules/eslint-plugin-jsdoc/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/edgedriver": { + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "hasInstallScript": true, + "dependencies": { + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", + "decamelize": "^6.0.0", + "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", + "node-fetch": "^3.3.2", + "which": "^4.0.0" }, - "engines": { - "node": ">=10" + "bin": { + "edgedriver": "bin/edgedriver.js" } }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, + "node_modules/ejs": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-3.1.10.tgz", + "integrity": "sha512-UeJmFfOrAQS8OJWPZ4qtgHyWExa088/MtK5UEyoJGFH67cDEXkZSviOiKRCZ4Xij0zxI3JECgYs3oKx+AizQBA==", "dev": true, "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" + "jake": "^10.8.5" }, - "engines": { - "node": ">=8.10.0" + "bin": { + "ejs": "bin/cli.js" }, - "peerDependencies": { - "eslint": ">=5.16.0" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", + "node_modules/electron-to-chromium": { + "version": "1.5.70", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.70.tgz", + "integrity": "sha512-P6FPqAWIZrC3sHDAwBitJBs7N7IF58m39XVny7DFseQXK2eiMn7nNQizFf63mWDDUnFvaqsM8FI0+ZZfLkdUGA==" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", "dev": true, "engines": { "node": ">= 4" } }, - "node_modules/eslint-plugin-prebid": { - "resolved": "plugins/eslint", - "link": true - }, - "node_modules/eslint-plugin-promise": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", - "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", "dev": true, "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" + "node": ">= 0.8" } }, - "node_modules/eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", + "node_modules/encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", "dev": true, - "peerDependencies": { - "eslint": ">=3.19.0" + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" } }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "node_modules/encoding-sniffer/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "dev": true, "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=0.10.0" } }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", + "node_modules/end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" + "once": "^1.4.0" } }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/engine.io": { + "version": "6.5.4", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.4.tgz", + "integrity": "sha512-KdVSDKhVKyOi+r5uEabrDLZw2qXStVvCsEB/LN3mw4WFi6Gx50jTyuxYVCwAAC0U46FdnzP/ScKRBTXb/NiEOg==", "dev": true, + "dependencies": { + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.11.0" + }, "engines": { - "node": ">=4" + "node": ">=10.2.0" } }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "node_modules/engine.io-parser": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz", + "integrity": "sha512-RcyUFKA93/CXH20l4SoVvzZfrSDMOTUS3bWVpTt2FuFP+XYrL8i8oonHP7WInRyVHXh0n/ORtoeiE1os+8qkSw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=10.0.0" } }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", + "node_modules/engine.io/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" + "engines": { + "node": ">= 0.6" } }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/enhanced-resolve": { + "version": "5.16.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", + "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", "dev": true, "dependencies": { - "color-convert": "^2.0.1" + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10.13.0" } }, - "node_modules/eslint/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/enquirer": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.4.1.tgz", + "integrity": "sha512-rRqJg/6gd538VHvR3PSrdRBb/1Vy2YfzHqzvbhGIQpDRKIa4FgV/54b5Q1xYSxOOwKvjXweS26E0Q+nAMwp2pQ==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "ansi-colors": "^4.1.1", + "strip-ansi": "^6.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=8.6" } }, - "node_modules/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/enquirer/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=7.0.0" + "node": ">=8" } }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "node_modules/ent": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", + "integrity": "sha512-GHrMyVZQWvTIdDtpiEXdHZnFQKzeO09apj8Cbl4pKWy4i0Oprcq17usfDt5aO63swf0JOeMWjWQE/LzgSRuWpA==", "dev": true }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", "dev": true, "engines": { - "node": ">=10" + "node": ">=0.12" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "node_modules/errno": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.8.tgz", + "integrity": "sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==", + "dev": true, + "dependencies": { + "prr": "~1.0.1" + }, + "bin": { + "errno": "cli.js" + } + }, + "node_modules/error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "dev": true, + "dependencies": { + "string-template": "~0.2.1" + } + }, + "node_modules/error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-abstract": { + "version": "1.23.3", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.3.tgz", + "integrity": "sha512-e+HfNH61Bj1X9/jLc5v1owaLYuHdeHHSQlkhCBiTK8rBvKaULl/beGMxwrMXjpYrv4pz22BlY570vVePA2ho4A==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "array-buffer-byte-length": "^1.0.1", + "arraybuffer.prototype.slice": "^1.0.3", + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.7", + "data-view-buffer": "^1.0.1", + "data-view-byte-length": "^1.0.1", + "data-view-byte-offset": "^1.0.0", + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.0.0", + "es-set-tostringtag": "^2.0.3", + "es-to-primitive": "^1.2.1", + "function.prototype.name": "^1.1.6", + "get-intrinsic": "^1.2.4", + "get-symbol-description": "^1.0.2", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2", + "has-proto": "^1.0.3", + "has-symbols": "^1.0.3", + "hasown": "^2.0.2", + "internal-slot": "^1.0.7", + "is-array-buffer": "^3.0.4", + "is-callable": "^1.2.7", + "is-data-view": "^1.0.1", + "is-negative-zero": "^2.0.3", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.3", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.13", + "is-weakref": "^1.0.2", + "object-inspect": "^1.13.1", + "object-keys": "^1.1.1", + "object.assign": "^4.1.5", + "regexp.prototype.flags": "^1.5.2", + "safe-array-concat": "^1.1.2", + "safe-regex-test": "^1.0.3", + "string.prototype.trim": "^1.2.9", + "string.prototype.trimend": "^1.0.8", + "string.prototype.trimstart": "^1.0.8", + "typed-array-buffer": "^1.0.2", + "typed-array-byte-length": "^1.0.1", + "typed-array-byte-offset": "^1.0.2", + "typed-array-length": "^1.0.6", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.15" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/semver": { - "version": "7.6.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", - "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", "dev": true, - "bin": { - "semver": "bin/semver.js" + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" }, - "engines": { - "node": ">=10" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/eslint/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/es-module-lexer": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.5.3.tgz", + "integrity": "sha512-i1gCgmR9dCl6Vil6UKPI/trA69s08g/syhiDK9TG0Nf1RJjjFI+AzoWW7sPufzkgYAn861skuCwJa0pIIHYxvg==", + "dev": true + }, + "node_modules/es-object-atoms": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.0.0.tgz", + "integrity": "sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==", "dev": true, "dependencies": { - "ansi-regex": "^5.0.1" + "es-errors": "^1.3.0" }, "engines": { - "node": ">=8" + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/es-set-tostringtag": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.3.tgz", + "integrity": "sha512-3T8uNMC3OQTHkFUsFq8r/BwAXLHvU/9O9mE0fBc/MY5iq/8H7ncvO947LmYA6ldWw9Uh8Yhf25zu6n7nML5QWQ==", "dev": true, - "engines": { - "node": ">=8" + "dependencies": { + "get-intrinsic": "^1.2.4", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 0.4" } }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/es-shim-unscopables": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.2.tgz", + "integrity": "sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" + "hasown": "^2.0.0" } }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", "dev": true, + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, "engines": { - "node": ">=10" + "node": ">= 0.4" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", + "node_modules/es5-ext": { + "version": "0.10.64", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz", + "integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==", "dev": true, + "hasInstallScript": true, "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "esniff": "^2.0.1", + "next-tick": "^1.1.0" }, "engines": { "node": ">=0.10" } }, - "node_modules/espree": { - "version": "7.3.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", - "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", + "node_modules/es5-shim": { + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/es5-shim/-/es5-shim-4.6.7.tgz", + "integrity": "sha512-jg21/dmlrNQI7JyyA2w7n+yifSxBng0ZralnSfVZjoCawgNTCnS+yBCyVM9DL5itm7SUnDGgv7hcq2XCZX4iRQ==", "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=0.4.0" } }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "node_modules/es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" } }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", + "node_modules/es6-promise": { + "version": "4.2.8", + "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", + "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==" + }, + "node_modules/es6-promisify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", + "integrity": "sha512-C+d6UdsYDk0lMebHNR4S2NybQMMngAOnOwYBQjTOiv0MkoJMP0Myw2mgpDLBcpfCmRLxyFqYhS/CfOENq4SJhQ==", "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "es6-promise": "^4.0.3" } }, - "node_modules/esquery": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", - "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "node_modules/es6-symbol": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz", + "integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==", "dev": true, "dependencies": { - "estraverse": "^5.1.0" + "d": "^1.0.2", + "ext": "^1.7.0" }, "engines": { - "node": ">=0.10" + "node": ">=0.12" } }, - "node_modules/esquery/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "node_modules/es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", "dev": true, - "engines": { - "node": ">=4.0" + "dependencies": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" } }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "node_modules/esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", "dev": true, - "dependencies": { - "estraverse": "^5.2.0" + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=4.0" + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" } }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", "engines": { - "node": ">=4.0" + "node": ">=6" } }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, "engines": { - "node": ">=4.0" + "node": ">=0.8.0" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "node_modules/escodegen": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", + "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", "dev": true, - "optional": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dependencies": { + "esprima": "^2.7.1", + "estraverse": "^1.9.1", + "esutils": "^2.0.2", + "optionator": "^0.8.1" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=0.12.0" + }, + "optionalDependencies": { + "source-map": "~0.2.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "node_modules/escodegen/node_modules/estraverse": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", + "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", + "dev": true, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/event-emitter": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", - "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", + "node_modules/escodegen/node_modules/levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", + "node_modules/escodegen/node_modules/optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, "dependencies": { - "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" + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + }, + "engines": { + "node": ">= 0.8.0" } }, - "node_modules/event-stream/node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", - "dev": true - }, - "node_modules/event-target-shim": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", - "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "node_modules/escodegen/node_modules/prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true, "engines": { - "node": ">=6" + "node": ">= 0.8.0" } }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "node_modules/escodegen/node_modules/source-map": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.2.0.tgz", + "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", "dev": true, + "optional": true, + "dependencies": { + "amdefine": ">=0.0.4" + }, "engines": { - "node": ">=0.8.x" + "node": ">=0.8.0" } }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", + "node_modules/escodegen/node_modules/type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "prelude-ls": "~1.1.2" }, "engines": { - "node": ">=6" + "node": ">= 0.8.0" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/eslint": { + "version": "7.32.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz", + "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==", "dev": true, "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "@babel/code-frame": "7.12.11", + "@eslint/eslintrc": "^0.4.3", + "@humanwhocodes/config-array": "^0.5.0", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.0.1", + "doctrine": "^3.0.0", + "enquirer": "^2.3.5", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^5.1.1", + "eslint-utils": "^2.1.0", + "eslint-visitor-keys": "^2.0.0", + "espree": "^7.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^5.1.2", + "globals": "^13.6.0", + "ignore": "^4.0.6", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^3.13.1", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "progress": "^2.0.0", + "regexpp": "^3.1.0", + "semver": "^7.2.1", + "strip-ansi": "^6.0.0", + "strip-json-comments": "^3.1.0", + "table": "^6.0.9", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "bin": { + "eslint": "bin/eslint.js" }, "engines": { - "node": ">=4.8" + "node": "^10.12.0 || >=12.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" } }, - "node_modules/execa/node_modules/isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true + "node_modules/eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", + "integrity": "sha512-UkFojTV1o0GOe1edOEiuI5ccYLJSuNngtqSeClNzhsmG8KPJ+7mRxgtp2oYhqZAK/brlXMoCd+VgXViE0AfyKw==", + "dev": true, + "peerDependencies": { + "eslint": ">=3.19.0", + "eslint-plugin-import": ">=2.2.0", + "eslint-plugin-node": ">=4.2.2", + "eslint-plugin-promise": ">=3.5.0", + "eslint-plugin-standard": ">=3.0.0" + } }, - "node_modules/execa/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "node_modules/eslint-import-resolver-node": { + "version": "0.3.9", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", "dev": true, - "engines": { - "node": ">=4" + "dependencies": { + "debug": "^3.2.7", + "is-core-module": "^2.13.0", + "resolve": "^1.22.4" } }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", - "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "node_modules/eslint-import-resolver-node/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "bin": { - "semver": "bin/semver" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "node_modules/eslint-module-utils": { + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "dependencies": { - "shebang-regex": "^1.0.0" + "debug": "^3.2.7" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" + }, + "peerDependenciesMeta": { + "eslint": { + "optional": true + } } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "node_modules/eslint-module-utils/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, - "engines": { - "node": ">=0.10.0" + "dependencies": { + "ms": "^2.1.1" } }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/eslint-plugin-es": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", + "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", "dev": true, "dependencies": { - "isexe": "^2.0.0" + "eslint-utils": "^2.0.0", + "regexpp": "^3.0.0" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=8.10.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=4.19.1" } }, - "node_modules/expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "node_modules/eslint-plugin-import": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "dependencies": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", + "array.prototype.flat": "^1.3.2", + "array.prototype.flatmap": "^1.3.2", + "debug": "^3.2.7", + "doctrine": "^2.1.0", + "eslint-import-resolver-node": "^0.3.9", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", + "is-glob": "^4.0.3", + "minimatch": "^3.1.2", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", + "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" } }, - "node_modules/expand-brackets/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/eslint-plugin-import/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", "dev": true, "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, - "node_modules/expand-brackets/node_modules/define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "node_modules/eslint-plugin-import/node_modules/doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", "dev": true, "dependencies": { - "is-descriptor": "^0.1.0" + "esutils": "^2.0.2" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/expand-brackets/node_modules/extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "node_modules/eslint-plugin-jsdoc": { + "version": "48.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", + "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", "dev": true, "dependencies": { - "is-extendable": "^0.1.0" + "@es-joy/jsdoccomment": "~0.43.1", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", + "debug": "^4.3.4", + "escape-string-regexp": "^4.0.0", + "esquery": "^1.5.0", + "parse-imports": "^2.1.0", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=18" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0" } }, - "node_modules/expand-brackets/node_modules/is-descriptor": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", - "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", + "node_modules/eslint-plugin-jsdoc/node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "dependencies": { - "is-accessor-descriptor": "^1.0.1", - "is-data-descriptor": "^1.0.1" - }, "engines": { - "node": ">= 0.4" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expand-brackets/node_modules/is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", + "node_modules/eslint-plugin-jsdoc/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, + "bin": { + "semver": "bin/semver.js" + }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/expand-brackets/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/expand-tilde": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", - "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", + "node_modules/eslint-plugin-jsdoc/node_modules/spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", "dev": true, "dependencies": { - "homedir-polyfill": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" } }, - "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "node_modules/eslint-plugin-node": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", + "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", "dev": true, "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "eslint-plugin-es": "^3.0.0", + "eslint-utils": "^2.0.0", + "ignore": "^5.1.1", + "minimatch": "^3.0.4", + "resolve": "^1.10.1", + "semver": "^6.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": ">=8.10.0" + }, + "peerDependencies": { + "eslint": ">=5.16.0" } }, - "node_modules/expect-webdriverio": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.13.0.tgz", - "integrity": "sha512-y2LF6vECifTOiOk0i0FMPWJGyntiW+eqzQWFZimu9Cae8XMUH5q4F5SzpfGCxsvmOdf3uFjlr+u9IXrQBN5o0Q==", + "node_modules/eslint-plugin-node/node_modules/ignore": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", + "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", "dev": true, - "dependencies": { - "@vitest/snapshot": "^1.2.2", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0" - }, "engines": { - "node": ">=16 || >=18 || >=20" - }, - "optionalDependencies": { - "@wdio/globals": "^8.29.3", - "@wdio/logger": "^8.28.0", - "webdriverio": "^8.29.3" + "node": ">= 4" } }, - "node_modules/expect-webdriverio/node_modules/@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "node_modules/eslint-plugin-prebid": { + "resolved": "plugins/eslint", + "link": true + }, + "node_modules/eslint-plugin-promise": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-5.2.0.tgz", + "integrity": "sha512-SftLb1pUG01QYq2A/hGAWfDRXqYD82zE7j7TopDOyNdU+7SvvoXREls/+PRTY17vUXzXnZA/zfnyKgRH6x4JJw==", "dev": true, - "optional": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, "engines": { - "node": ">=16.3.0" + "node": "^10.12.0 || >=12.0.0" }, "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "eslint": "^7.0.0" } }, - "node_modules/expect-webdriverio/node_modules/agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "node_modules/eslint-plugin-standard": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", + "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", "dev": true, - "optional": true, - "dependencies": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" + "peerDependencies": { + "eslint": ">=3.19.0" } }, - "node_modules/expect-webdriverio/node_modules/archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, - "optional": true, "dependencies": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" }, "engines": { - "node": ">= 14" + "node": ">=8.0.0" } }, - "node_modules/expect-webdriverio/node_modules/archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "node_modules/eslint-utils": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", + "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", "dev": true, - "optional": true, "dependencies": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "eslint-visitor-keys": "^1.1.0" }, "engines": { - "node": ">= 14" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" } }, - "node_modules/expect-webdriverio/node_modules/async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true - }, - "node_modules/expect-webdriverio/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", "dev": true, - "optional": true, - "dependencies": { - "balanced-match": "^1.0.0" + "engines": { + "node": ">=4" } }, - "node_modules/expect-webdriverio/node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "optional": true, - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "engines": { + "node": ">=10" } }, - "node_modules/expect-webdriverio/node_modules/buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "node_modules/eslint/node_modules/@babel/code-frame": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", + "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", "dev": true, - "optional": true, - "engines": { - "node": ">=8.0.0" + "dependencies": { + "@babel/highlight": "^7.10.4" } }, - "node_modules/expect-webdriverio/node_modules/chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", + "node_modules/eslint/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - }, - "bin": { - "print-chrome-path": "bin/print-chrome-path.js" + "color-convert": "^2.0.1" }, "engines": { - "node": ">=12.13.0" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/expect-webdriverio/node_modules/compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "optional": true, "dependencies": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 14" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/expect-webdriverio/node_modules/crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "node_modules/eslint/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "optional": true, "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 14" + "node": ">=7.0.0" } }, - "node_modules/expect-webdriverio/node_modules/cross-fetch": { + "node_modules/eslint/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", "dev": true, - "optional": true, - "dependencies": { - "node-fetch": "^2.6.12" + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-webdriverio/node_modules/devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" + "type-fest": "^0.20.2" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-webdriverio/node_modules/devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true, - "optional": true + "engines": { + "node": ">=8" + } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", + "node_modules/eslint/node_modules/semver": { + "version": "7.6.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", + "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, "bin": { - "browsers": "lib/cjs/main-cli.js" + "semver": "bin/semver.js" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=10" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "node_modules/eslint/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "4" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 6.0.0" + "node": ">=8" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", + "node_modules/eslint/node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.0" + "engines": { + "node": ">=8" }, - "peerDependencies": { - "devtools-protocol": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "node-fetch": "^2.6.11" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", "dev": true, - "optional": true, - "peer": true + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "node_modules/esniff": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", + "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" }, "engines": { - "node": ">= 6" + "node": ">=0.10" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "node_modules/espree": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz", + "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "acorn": "^7.4.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/espree/node_modules/eslint-visitor-keys": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", + "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", + "dev": true, + "engines": { + "node": ">=4" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", + "node_modules/esprima": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", + "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "estraverse": "^5.1.0" }, "engines": { - "node": ">= 6" + "node": ">=0.10" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", + "node_modules/esquery/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "engines": { + "node": ">=4.0" } }, - "node_modules/expect-webdriverio/node_modules/devtools/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "estraverse": "^5.2.0" }, "engines": { - "node": ">=6" + "node": ">=4.0" } }, - "node_modules/expect-webdriverio/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", "dev": true, - "optional": true, - "peer": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4.0" } }, - "node_modules/expect-webdriverio/node_modules/https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, - "optional": true, - "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" - }, "engines": { - "node": ">= 14" + "node": ">=4.0" } }, - "node_modules/expect-webdriverio/node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "dev": true, - "optional": true, + "optional": true + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "d": "1", + "es5-ext": "~0.10.14" } }, - "node_modules/expect-webdriverio/node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/event-stream": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", + "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", "dev": true, - "optional": true, - "peer": true, "dependencies": { - "ms": "2.0.0" + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" } }, - "node_modules/expect-webdriverio/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "node_modules/event-stream/node_modules/map-stream": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", + "integrity": "sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==", + "dev": true + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", "dev": true, - "optional": true, "engines": { - "node": ">=12" + "node": ">=6" } }, - "node_modules/expect-webdriverio/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "node_modules/eventemitter3": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", + "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", + "dev": true + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", "dev": true, - "optional": true, - "dependencies": { - "brace-expansion": "^2.0.1" - }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=0.8.x" } }, - "node_modules/expect-webdriverio/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node_modules/expect-webdriverio/node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "node_modules/execa": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", + "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", "dev": true, - "optional": true, "dependencies": { - "whatwg-url": "^5.0.0" + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=6" } }, - "node_modules/expect-webdriverio/node_modules/proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "dev": true, - "optional": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" }, "engines": { - "node": ">= 14" + "node": ">=4.8" } }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", + "node_modules/execa/node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", "dev": true, - "optional": true, "dependencies": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" + "shebang-regex": "^1.0.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", "dev": true, - "optional": true + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/expect-webdriverio/node_modules/readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "optional": true, "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "isexe": "^2.0.0" }, - "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "bin": { + "which": "bin/which" } }, - "node_modules/expect-webdriverio/node_modules/serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", + "node_modules/expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", "dev": true, - "optional": true, "dependencies": { - "type-fest": "^2.12.2" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "node_modules/expand-brackets/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", "dev": true, - "optional": true, "dependencies": { - "safe-buffer": "~5.2.0" + "ms": "2.0.0" } }, - "node_modules/expect-webdriverio/node_modules/type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "node_modules/expand-brackets/node_modules/define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", "dev": true, - "optional": true, - "engines": { - "node": ">=12.20" + "dependencies": { + "is-descriptor": "^0.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", + "node_modules/expand-brackets/node_modules/extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - }, - { - "type": "github", - "url": "https://github.com/sponsors/faisalman" - } - ], - "optional": true, - "peer": true, + "dependencies": { + "is-extendable": "^0.1.0" + }, "engines": { - "node": "*" + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "node_modules/expand-brackets/node_modules/is-descriptor": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.7.tgz", + "integrity": "sha512-C3grZTvObeN1xud4cRWl366OMXZTj0+HGyk4hvfpx4ZHt1Pb60ANSXqCK7pdOTeUQpRzECBSTphqvD7U+l22Eg==", "dev": true, - "optional": true, "dependencies": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "is-accessor-descriptor": "^1.0.1", + "is-data-descriptor": "^1.0.1" }, "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } + "node": ">= 0.4" } }, - "node_modules/expect-webdriverio/node_modules/ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", + "node_modules/expand-brackets/node_modules/is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==", "dev": true, - "optional": true, "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "node_modules/expand-brackets/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/expand-tilde": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", + "integrity": "sha512-A5EmesHW6rfnZ9ysHQjPdJRni0SRar0tjtG5MNtm9n5TUvsYU8oozprtRD4AqHxcZWWlVuAmQo2nWKfN9oyjTw==", "dev": true, - "optional": true, "dependencies": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" + "homedir-polyfill": "^1.0.1" }, "engines": { - "node": ">=12" + "node": ">=0.10.0" } }, - "node_modules/expect-webdriverio/node_modules/zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "node_modules/expect": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", + "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", "dev": true, - "optional": true, "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@jest/expect-utils": "^29.7.0", + "jest-get-type": "^29.6.3", + "jest-matcher-utils": "^29.7.0", + "jest-message-util": "^29.7.0", + "jest-util": "^29.7.0" }, "engines": { - "node": ">= 14" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, "node_modules/express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -13548,11 +10657,66 @@ "ms": "2.0.0" } }, + "node_modules/express/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/express/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/express/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/express/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/express/node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -13766,6 +10930,28 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/NaturalIntelligence" + }, + { + "type": "paypal", + "url": "https://paypal.me/naturalintelligence" + } + ], + "dependencies": { + "strnum": "^1.0.5" + }, + "bin": { + "fxparser": "src/cli/cli.js" + } + }, "node_modules/faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -13826,28 +11012,15 @@ } }, "node_modules/figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "dependencies": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "engines": { - "node": ">=14" + "is-unicode-supported": "^2.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -13903,9 +11076,9 @@ } }, "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "dependencies": { "to-regex-range": "^5.0.1" @@ -13915,12 +11088,12 @@ } }, "node_modules/finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "dependencies": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -13939,6 +11112,14 @@ "ms": "2.0.0" } }, + "node_modules/finalhandler/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -14119,15 +11300,6 @@ "node": ">=0.10.0" } }, - "node_modules/findup-sync/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/findup-sync/node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -14298,9 +11470,9 @@ "dev": true }, "node_modules/foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "dependencies": { "cross-spawn": "^7.0.0", @@ -14354,15 +11526,6 @@ "node": ">= 0.12" } }, - "node_modules/form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true, - "engines": { - "node": ">= 14.17" - } - }, "node_modules/formdata-node": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", @@ -14422,12 +11585,6 @@ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, - "node_modules/fs-constants": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", - "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", - "dev": true - }, "node_modules/fs-extra": { "version": "0.6.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", @@ -14531,6 +11688,7 @@ "version": "1.0.12", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz", "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==", + "deprecated": "This package is no longer supported.", "dev": true, "dependencies": { "graceful-fs": "^4.1.2", @@ -14566,6 +11724,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -14623,18 +11782,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "dependencies": { - "globule": "^1.0.0" - }, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/geckodriver": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.0.tgz", @@ -14791,6 +11938,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "dependencies": { + "resolve-pkg-maps": "^1.0.0" + }, + "funding": { + "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" + } + }, "node_modules/get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -14909,23 +12068,21 @@ "dev": true }, "node_modules/glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "bin": { "glob": "dist/esm/bin.mjs" }, - "engines": { - "node": ">=16 || 14 >=14.18" - }, "funding": { "url": "https://github.com/sponsors/isaacs" } @@ -15094,7 +12251,6 @@ "version": "2.1.8", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-2.1.8.tgz", "integrity": "sha512-ZmZUazfOzf0Nve7duiCKD23PFSCs4JPoYyccjUFF3aQkQadqBhfzhjkwBH2mNOG9cTBwhamM37EIsIkZw3nRgg==", - "deprecated": "Chokidar 2 does not receive security updates since 2019. Upgrade to chokidar 3 with 15x fewer dependencies", "dev": true, "dependencies": { "anymatch": "^2.0.0", @@ -15220,25 +12376,25 @@ "node": ">=0.10.0" } }, - "node_modules/glob-watcher/node_modules/is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "node_modules/glob-watcher/node_modules/is-number/node_modules/kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", "dev": true, "dependencies": { - "isobject": "^3.0.1" + "is-buffer": "^1.1.5" }, "engines": { "node": ">=0.10.0" } }, - "node_modules/glob-watcher/node_modules/kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "node_modules/glob-watcher/node_modules/is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "dependencies": { - "is-buffer": "^1.1.5" + "isobject": "^3.0.1" }, "engines": { "node": ">=0.10.0" @@ -15293,15 +12449,6 @@ "node": ">=0.10.0" } }, - "node_modules/glob-watcher/node_modules/micromatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/glob-watcher/node_modules/readdirp": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-2.2.1.tgz", @@ -15447,52 +12594,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "dependencies": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/globule/node_modules/glob": { - "version": "7.1.7", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", - "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", - "dev": true, - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/globule/node_modules/minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/glogg": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", @@ -15516,43 +12617,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -15622,6 +12686,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -15948,117 +13013,6 @@ "node": ">=6" } }, - "node_modules/gulp-connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/gulp-connect/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-connect/node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/gulp-connect/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/mime": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", - "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", - "dev": true, - "bin": { - "mime": "cli.js" - } - }, - "node_modules/gulp-connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/gulp-connect/node_modules/send": { - "version": "0.16.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", - "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", - "dev": true, - "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "~1.6.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "statuses": "~1.4.0" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/gulp-connect/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/gulp-connect/node_modules/statuses": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", - "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/gulp-eslint": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/gulp-eslint/-/gulp-eslint-6.0.0.tgz", @@ -16524,6 +13478,7 @@ "version": "2.6.3", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -17442,6 +14397,7 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, "engines": { "node": ">=4" } @@ -17746,19 +14702,6 @@ "node": ">=12.0.0" } }, - "node_modules/home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "dev": true, - "dependencies": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/homedir-polyfill": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz", @@ -17808,12 +14751,31 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "node_modules/htmlfy": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.3.2.tgz", + "integrity": "sha512-FsxzfpeDYRqn1emox9VpxMPfGjADoUmmup8D604q497R0VNxiXs4ZZTN2QzkaMA5C9aHGUoe1iQRVSm+HK9xuA==", "dev": true }, + "node_modules/htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "node_modules/http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -17889,19 +14851,6 @@ "npm": ">=1.3.7" } }, - "node_modules/http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - }, - "engines": { - "node": ">=10.19.0" - } - }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -17916,12 +14865,12 @@ } }, "node_modules/human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true, "engines": { - "node": ">=16.17.0" + "node": ">=18.18.0" } }, "node_modules/iconv-lite": { @@ -17964,6 +14913,12 @@ "node": ">= 4" } }, + "node_modules/immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -18018,6 +14973,7 @@ "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, "dependencies": { "once": "^1.3.0", @@ -18039,53 +14995,22 @@ } }, "node_modules/inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.1.0.tgz", + "integrity": "sha512-CmLAZT65GG/v30c+D2Fk8+ceP6pxD6RL+hIUOWAltCmeyEqWYwqu9v76q03OvjyZ3AB0C1Ala2stn1z/rMqGEw==", "dev": true, "dependencies": { - "@ljharb/through": "^2.3.11", + "@inquirer/core": "^9.2.1", + "@inquirer/prompts": "^6.0.1", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", + "mute-stream": "^1.0.0", "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "engines": { - "node": ">=14.18.0" - } - }, - "node_modules/inquirer/node_modules/chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/inquirer/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" + "rxjs": "^7.8.1" }, "engines": { - "node": ">=8" + "node": ">=18" } }, "node_modules/internal-slot": { @@ -18111,15 +15036,6 @@ "node": ">= 0.10" } }, - "node_modules/invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "dependencies": { - "loose-envify": "^1.0.0" - } - }, "node_modules/invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -18303,11 +15219,14 @@ } }, "node_modules/is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "dependencies": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" }, "funding": { "url": "https://github.com/sponsors/ljharb" @@ -18414,18 +15333,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-finite": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.1.0.tgz", - "integrity": "sha512-cdyMtqX/BOqqNBBiKlIVkytNHm49MtMlYyn1zxzvJKWmFMlGzm+ry5BBfYyeY9YmNKbRSo/o7OX9w9ale0wg3w==", - "dev": true, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -18468,15 +15375,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-interactive": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", - "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/is-map": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -18720,12 +15618,12 @@ } }, "node_modules/is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -19001,6 +15899,7 @@ "version": "5.0.15", "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "inflight": "^1.0.4", @@ -19075,16 +15974,13 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "dependencies": { "@isaacs/cliui": "^8.0.2" }, - "engines": { - "node": ">=14" - }, "funding": { "url": "https://github.com/sponsors/isaacs" }, @@ -19443,15 +16339,6 @@ "node": ">=8" } }, - "node_modules/jest-message-util/node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/jest-message-util/node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -19625,23 +16512,23 @@ "dev": true }, "node_modules/jsdoc-type-pratt-parser": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", - "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true, "engines": { "node": ">=12.0.0" } }, "node_modules/jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==", "bin": { "jsesc": "bin/jsesc" }, "engines": { - "node": ">=4" + "node": ">=6" } }, "node_modules/json-buffer": { @@ -19715,6 +16602,18 @@ "node": ">=0.6.0" } }, + "node_modules/jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "dependencies": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "node_modules/just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -19932,6 +16831,7 @@ "version": "2.7.1", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -20333,9 +17233,9 @@ } }, "node_modules/kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, "engines": { "node": ">=0.10.0" @@ -20368,18 +17268,6 @@ "@babel/traverse": "^7.10.5" } }, - "node_modules/ky": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", - "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" - } - }, "node_modules/last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -20451,6 +17339,15 @@ "node": ">= 0.8.0" } }, + "node_modules/lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "dependencies": { + "immediate": "~3.0.5" + } + }, "node_modules/liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -20482,31 +17379,6 @@ "node": ">=0.10.0" } }, - "node_modules/lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "node_modules/lighthouse-logger/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/lighthouse-logger/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, "node_modules/lines-and-columns": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", @@ -20523,23 +17395,23 @@ "dev": true }, "node_modules/live-connect-common": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", - "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-4.1.0.tgz", + "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==", "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/live-connect-js": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", - "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", + "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", "dependencies": { - "live-connect-common": "^v3.1.4", + "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" }, "engines": { - "node": ">=18" + "node": ">=20" } }, "node_modules/livereload-js": { @@ -20741,18 +17613,6 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "node_modules/lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "node_modules/lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, "node_modules/lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -20762,12 +17622,6 @@ "lodash._root": "^3.0.0" } }, - "node_modules/lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, "node_modules/lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -20798,18 +17652,6 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, - "node_modules/lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", - "dev": true - }, - "node_modules/lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, "node_modules/lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -20980,18 +17822,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/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, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -21001,18 +17831,6 @@ "get-func-name": "^2.0.1" } }, - "node_modules/lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -21042,12 +17860,12 @@ } }, "node_modules/magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.14", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", + "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", "dev": true, "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -21077,15 +17895,6 @@ "node": ">=0.10.0" } }, - "node_modules/make-iterator/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", @@ -21123,12 +17932,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true - }, "node_modules/matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -21301,15 +18104,6 @@ "node": ">=0.10.0" } }, - "node_modules/matchdep/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/matchdep/node_modules/micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -21654,19 +18448,22 @@ } }, "node_modules/memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", "dev": true, "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", + "d": "^1.0.2", + "es5-ext": "^0.10.64", "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" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/memory-fs": { @@ -21683,9 +18480,12 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/merge-stream": { "version": "2.0.0", @@ -22265,12 +19065,12 @@ ] }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -22317,18 +19117,6 @@ "node": ">=6" } }, - "node_modules/mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -22360,20 +19148,14 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true, "engines": { "node": ">=16 || 14 >=14.17" } }, - "node_modules/mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", - "dev": true - }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -22406,31 +19188,31 @@ "dev": true }, "node_modules/mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "bin": { "_mocha": "bin/_mocha", @@ -22440,15 +19222,6 @@ "node": ">= 14.0.0" } }, - "node_modules/mocha/node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/mocha/node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -22507,33 +19280,6 @@ "node": ">=8" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, "node_modules/mocha/node_modules/cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -22563,15 +19309,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "node_modules/mocha/node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/mocha/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -22684,9 +19421,9 @@ } }, "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "dependencies": { "brace-expansion": "^2.0.1" @@ -22806,9 +19543,9 @@ } }, "node_modules/mocha/node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "version": "20.2.9", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz", + "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==", "dev": true, "engines": { "node": ">=10" @@ -22914,7 +19651,8 @@ "node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "node_modules/multipipe": { "version": "0.1.2", @@ -23030,15 +19768,6 @@ "node": ">=0.10.0" } }, - "node_modules/nanomatch/node_modules/kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -23128,9 +19857,9 @@ } }, "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dev": true, "dependencies": { "isarray": "0.0.1" @@ -23184,9 +19913,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node_modules/node-request-interceptor": { "version": "0.6.3", @@ -23260,18 +19989,6 @@ "node": ">=0.10.0" } }, - "node_modules/normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -23657,139 +20374,6 @@ "node": ">= 0.8.0" } }, - "node_modules/ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "dependencies": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/ora/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/ora/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/ora/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/ora/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/ora/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/ora/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -23799,15 +20383,6 @@ "readable-stream": "^2.0.1" } }, - "node_modules/os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -23829,15 +20404,6 @@ "node": ">=0.10.0" } }, - "node_modules/p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true, - "engines": { - "node": ">=12.20" - } - }, "node_modules/p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", @@ -23847,15 +20413,6 @@ "node": ">=4" } }, - "node_modules/p-iteration": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz", - "integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -23949,6 +20506,18 @@ "node": ">= 14" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "dev": true + }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", + "dev": true + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -23975,6 +20544,19 @@ "node": ">=0.8" } }, + "node_modules/parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "dependencies": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + }, + "engines": { + "node": ">= 18" + } + }, "node_modules/parse-json": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", @@ -24057,6 +20639,55 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "dependencies": { + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-parser-stream/node_modules/parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "dependencies": { + "entities": "^4.5.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -24159,9 +20790,9 @@ } }, "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "node_modules/path-type": { "version": "1.1.0", @@ -24223,9 +20854,9 @@ "dev": true }, "node_modules/picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "node_modules/picomatch": { "version": "2.3.1", @@ -24272,6 +20903,15 @@ "node": ">=0.10.0" } }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, "node_modules/pkcs7": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", @@ -24433,15 +21073,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -24631,102 +21262,11 @@ "node": ">=6" } }, - "node_modules/puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", - "dev": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.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": "8.5.0" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/puppeteer-core/node_modules/devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true - }, - "node_modules/puppeteer-core/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/puppeteer-core/node_modules/tar-fs": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz", - "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==", - "dev": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "node_modules/puppeteer-core/node_modules/tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", - "dev": true, - "dependencies": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/puppeteer-core/node_modules/ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==", + "deprecated": "You or someone you depend on is using Q, the JavaScript Promise library that gave JavaScript developers strong feelings about promises. They can almost certainly migrate to the native JavaScript promise now. Thank you literally everyone for joining me in this bet against the odds. Be excellent to each other.\n\n(For a CapTP with native promises, see @endo/eventual-send and @endo/captp)", "dev": true, "engines": { "node": ">=0.6.0", @@ -24743,11 +21283,11 @@ } }, "node_modules/qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "dependencies": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" }, "engines": { "node": ">=0.6" @@ -24774,18 +21314,6 @@ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -25143,15 +21671,6 @@ "node": ">=4" } }, - "node_modules/regextras": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", - "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", - "dev": true, - "engines": { - "node": ">=0.1.14" - } - }, "node_modules/regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -25347,18 +21866,6 @@ "node": ">=0.10" } }, - "node_modules/repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "dependencies": { - "is-finite": "^1.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -25490,12 +21997,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "node_modules/resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -25530,6 +22031,15 @@ "node": ">= 0.10" } }, + "node_modules/resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true, + "funding": { + "url": "https://github.com/privatenumber/resolve-pkg-maps?sponsor=1" + } + }, "node_modules/resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", @@ -25537,21 +22047,6 @@ "deprecated": "https://github.com/lydell/resolve-url#deprecated", "dev": true }, - "node_modules/responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "dependencies": { - "lowercase-keys": "^3.0.0" - }, - "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/resq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", @@ -25605,6 +22100,7 @@ "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.1.3" @@ -25664,9 +22160,9 @@ } }, "node_modules/rxjs/node_modules/tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true }, "node_modules/sade": { @@ -25832,23 +22328,24 @@ } }, "node_modules/send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "dev": true, "dependencies": { "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", + "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": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" }, "engines": { "node": ">= 0.8.0" @@ -25858,62 +22355,120 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "dependencies": { "ms": "2.0.0" } }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "node_modules/send/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "node_modules/send/node_modules/http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/send/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true }, "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true, "bin": { "mime": "cli.js" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "node_modules/send/node_modules/on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "dependencies": { + "ee-first": "1.1.1" }, "engines": { - "node": ">=4" + "node": ">= 0.8" } }, - "node_modules/send/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "node_modules/send/node_modules/setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", + "dev": true + }, + "node_modules/send/node_modules/statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==", + "dev": true, + "engines": { + "node": ">= 0.6" + } }, "node_modules/serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "dependencies": { - "type-fest": "^0.20.2" + "type-fest": "^2.12.2" }, "engines": { - "node": ">=10" + "node": ">=14.16" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serialize-error/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true, "engines": { - "node": ">=10" + "node": ">=12.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "dependencies": { "randombytes": "^2.1.0" @@ -25998,19 +22553,87 @@ } }, "node_modules/serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "dependencies": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/serve-static/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/serve-static/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static/node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/serve-static/node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/serve-static/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/serve-static/node_modules/send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" }, "engines": { "node": ">= 0.8.0" } }, + "node_modules/serve-static/node_modules/send/node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", @@ -26107,6 +22730,18 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "dependencies": { + "kind-of": "^6.0.2" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -26192,14 +22827,20 @@ } }, "node_modules/slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, + "node_modules/slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", + "dev": true + }, "node_modules/slice-ansi": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", @@ -26433,6 +23074,7 @@ "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "~4.3.4", "ws": "~8.11.0" @@ -26528,12 +23170,22 @@ } }, "node_modules/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.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "dependencies": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/source-map-support/node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" } }, "node_modules/source-map-url": { @@ -27078,12 +23730,12 @@ } }, "node_modules/strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -27101,10 +23753,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "dependencies": { "has-flag": "^3.0.0" }, @@ -27133,6 +23792,28 @@ "es6-symbol": "^3.1.1" } }, + "node_modules/synckit": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", + "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "dev": true, + "dependencies": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/unts" + } + }, + "node_modules/synckit/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + }, "node_modules/table": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", @@ -27250,6 +23931,7 @@ "version": "2.5.4", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", "integrity": "sha512-Lw7SHMjssciQb/rRz7JyPIy9+bbUshEucPoLRvWqy09vC5zQixl8Uet+Zl+SROBB/JMWHJRdCk1qdxNWHNMvlQ==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "dependencies": { "glob": "^7.0.5" @@ -27387,25 +24069,6 @@ "node": ">=0.4.0" } }, - "node_modules/terser/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/terser/node_modules/source-map-support": { - "version": "0.5.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "dependencies": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } - }, "node_modules/test-exclude": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-6.0.0.tgz", @@ -27517,13 +24180,16 @@ } }, "node_modules/timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", "dev": true, "dependencies": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" + }, + "engines": { + "node": ">=0.12" } }, "node_modules/tiny-hashes": { @@ -27554,6 +24220,15 @@ "ms": "^2.1.1" } }, + "node_modules/tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -27576,14 +24251,6 @@ "node": ">=0.10.0" } }, - "node_modules/to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", - "engines": { - "node": ">=4" - } - }, "node_modules/to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -27706,12 +24373,6 @@ "node": ">=0.8" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "node_modules/traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -27731,15 +24392,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -27794,6 +24446,25 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "node_modules/tsx": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", + "dev": true, + "dependencies": { + "esbuild": "~0.23.0", + "get-tsconfig": "^4.7.5" + }, + "bin": { + "tsx": "dist/cli.mjs" + }, + "engines": { + "node": ">=18.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + } + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -27987,9 +24658,9 @@ } }, "node_modules/uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", "dev": true, "optional": true, "bin": { @@ -28069,6 +24740,15 @@ "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", "dev": true }, + "node_modules/undici": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", + "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "dev": true, + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -28111,6 +24791,18 @@ "node": ">=4" } }, + "node_modules/unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -28374,9 +25066,9 @@ } }, "node_modules/update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "funding": [ { "type": "opencollective", @@ -28392,8 +25084,8 @@ } ], "dependencies": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" }, "bin": { "update-browserslist-db": "cli.js" @@ -28465,6 +25157,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "node_modules/use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -28510,9 +25208,9 @@ } }, "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "dev": true, "funding": [ "https://github.com/sponsors/broofa", @@ -28783,23 +25481,21 @@ "dev": true }, "node_modules/videojs-ima": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-1.11.0.tgz", - "integrity": "sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.3.0.tgz", + "integrity": "sha512-8r0BZGT+WCTO6PePyKZHikV79Ojqh4yLMx4+DmPyXeRcKUVsQ7Va0R7Ok8GRcA8Zy3l1PM6jzLrD/W1rwKhZ8g==", "dev": true, "dependencies": { "@hapi/cryptiles": "^5.1.0", - "can-autoplay": "^3.0.0", + "can-autoplay": "^3.0.2", "extend": ">=3.0.2", - "lodash": ">=4.17.19", - "lodash.template": ">=4.5.0", - "videojs-contrib-ads": "^6.6.5" + "videojs-contrib-ads": "^6.9.0" }, "engines": { "node": ">=0.8.0" }, "peerDependencies": { - "video.js": "^5.19.2 || ^6 || ^7" + "video.js": "^5.19.2 || ^6 || ^7 || ^8" } }, "node_modules/videojs-playlist": { @@ -29084,6 +25780,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "optional": true, "dependencies": { "defaults": "^1.0.3" } @@ -29108,509 +25805,478 @@ } }, "node_modules/webdriver": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.36.1.tgz", - "integrity": "sha512-547RivYCHStVqtiGQBBcABAkzJbPnAWsxpXGzmj5KL+TOM2JF41N2iQRtUxXqr0jme1Nzzye7WS7Y7iSnK6i1g==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.4.1.tgz", + "integrity": "sha512-vFDdxMj/9W1+6jhpHSiRYfO8dix23HjAUtLx7aOv9ejEsntC0EzCIAftJ59YsF3Ppu184+FkdDVhnivpkZPTFw==", "dev": true, "dependencies": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "deepmerge-ts": "^5.1.0", - "got": "^12.6.1", - "ky": "^0.33.0", + "@wdio/config": "9.2.8", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", "ws": "^8.8.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18.20.0" } }, - "node_modules/webdriverio": { - "version": "7.36.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", - "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", - "dev": true, - "dependencies": { - "@types/aria-query": "^5.0.0", - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/repl": "7.33.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "archiver": "^5.0.0", - "aria-query": "^5.2.1", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.35.0", - "devtools-protocol": "^0.0.1260888", - "fs-extra": "^11.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^6.0.4", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.33.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/webdriverio/node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, - "node_modules/webdriverio/node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "node_modules/webdriver/node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, - "engines": { - "node": ">=10" - } - }, - "node_modules/webdriverio/node_modules/@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", - "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } - }, - "node_modules/webdriverio/node_modules/@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", - "dev": true, - "dependencies": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" + "bin": { + "browsers": "lib/cjs/main-cli.js" }, "engines": { - "node": ">=12.0.0" + "node": ">=18" } }, - "node_modules/webdriverio/node_modules/@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "dependencies": { - "chalk": "^4.0.0", + "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/webdriverio/node_modules/@wdio/repl": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", - "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", + "node_modules/webdriver/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "dependencies": { - "@wdio/utils": "7.33.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=12.0.0" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", + "node_modules/webdriver/node_modules/@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "got": "^11.8.1" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "^4.6.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", + "node_modules/webdriver/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "debug": "^4.3.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "node_modules/webdriver/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, "engines": { - "node": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/webdriverio/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "node_modules/webdriver/node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", + "dev": true, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/webdriver/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "balanced-match": "^1.0.0" + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", + "node_modules/webdriver/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "engines": { - "node": ">=10.6.0" + "node": ">=12" } }, - "node_modules/webdriverio/node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "node_modules/webdriver/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" }, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/webdriver/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "bin": { + "semver": "bin/semver.js" }, "engines": { "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/webdriverio/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/webdriver/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=7.0.0" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/webdriverio/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/webdriverio/node_modules/fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "node_modules/webdriver/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=14.14" + "node": ">=12" } }, - "node_modules/webdriverio/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" + "node_modules/webdriverio": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.4.1.tgz", + "integrity": "sha512-XIPtRnxSES4CoxH2BfUY/6QzNgEgJEUjMYu7t7SJR8bVfbLRVXAA1ie9kM0MtdLs4oZ2Pr8rR8fqytsA1CjEWw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.2.8", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.3.0", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.4.1" }, "engines": { - "node": ">=8" + "node": ">=18.20.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "peerDependencies": { + "puppeteer-core": "^22.3.0" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } } }, - "node_modules/webdriverio/node_modules/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" }, - "engines": { - "node": ">=12" + "bin": { + "browsers": "lib/cjs/main-cli.js" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=18" } }, - "node_modules/webdriverio/node_modules/glob/node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "node_modules/webdriverio/node_modules/@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=10" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "node_modules/webdriverio/node_modules/@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/webdriverio/node_modules/@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", "dev": true, + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + }, "engines": { - "node": ">=8" + "node": ">=18.20.0" } }, - "node_modules/webdriverio/node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "node_modules/webdriverio/node_modules/agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "debug": "^4.3.4" }, "engines": { - "node": ">=10.19.0" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/webdriverio/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "balanced-match": "^1.0.0" } }, - "node_modules/webdriverio/node_modules/ky": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", - "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", + "node_modules/webdriverio/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true, "engines": { - "node": ">=12" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/webdriverio/node_modules/lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "node_modules/webdriverio/node_modules/deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true, "engines": { - "node": ">=8" + "node": ">=16.0.0" } }, - "node_modules/webdriverio/node_modules/minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "node_modules/webdriverio/node_modules/https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "dependencies": { - "brace-expansion": "^2.0.1" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "node_modules/webdriverio/node_modules/lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "node_modules/webdriverio/node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "node_modules/webdriverio/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=8" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/webdriverio/node_modules/responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "node_modules/webdriverio/node_modules/proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "dependencies": { - "lowercase-keys": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">= 14" } }, - "node_modules/webdriverio/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/webdriverio/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/webdriverio/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/webdriverio/node_modules/tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "engines": { - "node": ">=8" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "node_modules/webdriverio/node_modules/webdriver": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", - "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", + "node_modules/webdriverio/node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "got": "^11.0.2", - "ky": "0.30.0", - "lodash.merge": "^4.6.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" }, "engines": { - "node": ">=12.0.0" + "node": ">=12" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, "node_modules/webpack": { "version": "5.91.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", @@ -29718,10 +26384,11 @@ } }, "node_modules/webpack-bundle-analyzer/node_modules/ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8.3.0" }, @@ -29982,14 +26649,37 @@ "node": ">=0.8.0" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "engines": { + "node": ">=18" } }, "node_modules/which": { @@ -30110,9 +26800,9 @@ "dev": true }, "node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "node_modules/wrap-ansi": { @@ -30387,73 +27077,91 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "node_modules/yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", "dev": true, - "dependencies": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" + "engines": { + "node": ">=18" }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true, "engines": { - "node": ">= 10" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/zip-stream/node_modules/archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "node_modules/zip-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "dependencies": { - "glob": "^7.2.3", - "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": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 14" } }, - "node_modules/zip-stream/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "node_modules/zip-stream/node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "node_modules/zip-stream/node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": ">= 6" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/zip-stream/node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/zwitch": { @@ -30484,34 +27192,35 @@ } }, "@babel/code-frame": { - "version": "7.24.2", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.2.tgz", - "integrity": "sha512-y5+tLQyV8pg3fsiln67BVLD1P13Eg4lh5RW9mF0zUuvLrv9uIQ4MCL+CRT+FTsBlBjcIan6PGsLcBN0m3ClUyQ==", + "version": "7.26.2", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.26.2.tgz", + "integrity": "sha512-RJlIHRueQgwWitWgF8OdFYGZX328Ax5BCemNGlqHfplnRT9ESi8JkFlvaVYbS+UubVY6dpv87Fs2u5M29iNFVQ==", "requires": { - "@babel/highlight": "^7.24.2", + "@babel/helper-validator-identifier": "^7.25.9", + "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/compat-data": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.24.4.tgz", - "integrity": "sha512-vg8Gih2MLK+kOkHJp4gBEIkyaIi00jgWot2D9QOmmfLC8jINSOzmCLta6Bvz/JSBCqnegV0L80jhxkol5GWNfQ==" + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.26.3.tgz", + "integrity": "sha512-nHIxvKPniQXpmQLb0vhY3VaFb3S0YrTAwpOWJZh1wn3oJPjJk9Asva204PsBdmAE8vpzfHudT8DB0scYvy9q0g==" }, "@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.0.tgz", + "integrity": "sha512-i1SLeK+DzNnQ3LL/CswPCa/E5u4lh1k6IAEphON8F+cXt0t9euTshDru0q7/IqMa1PMPz5RnHuHscF8/ZJsStg==", "requires": { "@ampproject/remapping": "^2.2.0", - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-module-transforms": "^7.24.5", - "@babel/helpers": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5", + "@babel/code-frame": "^7.26.0", + "@babel/generator": "^7.26.0", + "@babel/helper-compilation-targets": "^7.25.9", + "@babel/helper-module-transforms": "^7.26.0", + "@babel/helpers": "^7.26.0", + "@babel/parser": "^7.26.0", + "@babel/template": "^7.25.9", + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.26.0", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -30531,14 +27240,15 @@ } }, "@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.26.3.tgz", + "integrity": "sha512-6FF/urZvD0sTeO7k6/B15pMLC4CHUv1426lzr3N01aHJTl046uCAh9LXW/fzeXXjPNCJ6iABW5XaWOsIZB93aQ==", "requires": { - "@babel/types": "^7.24.5", + "@babel/parser": "^7.26.3", + "@babel/types": "^7.26.3", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", - "jsesc": "^2.5.1" + "jsesc": "^3.0.2" } }, "@babel/helper-annotate-as-pure": { @@ -30558,13 +27268,13 @@ } }, "@babel/helper-compilation-targets": { - "version": "7.23.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", - "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.9.tgz", + "integrity": "sha512-j9Db8Suy6yV/VHa4qzrj9yZfZxhLWQdVnRlXxmKLYlhWUVB1sB2G5sxuWYXk/whHD9iW76PmNzxZ4UCnTQTVEQ==", "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.9", + "@babel/helper-validator-option": "^7.25.9", + "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" } @@ -30613,20 +27323,20 @@ "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" }, "@babel/helper-function-name": { - "version": "7.23.0", - "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", - "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.24.7.tgz", + "integrity": "sha512-FyoJTsj/PEUWu1/TYRiXTIHc8lbw+TDYkZuoE43opPS5TrI7MyONBE1oNvfguEXAD9yhQRrVBnXdXzSLQl9XnA==", "requires": { - "@babel/template": "^7.22.15", - "@babel/types": "^7.23.0" + "@babel/template": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-hoist-variables": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", - "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.24.7.tgz", + "integrity": "sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-member-expression-to-functions": { @@ -30638,23 +27348,22 @@ } }, "@babel/helper-module-imports": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.3.tgz", - "integrity": "sha512-viKb0F9f2s0BCS22QSF308z/+1YWKV/76mwt61NBzS5izMzDPwdq1pTrzf+Li3npBWX9KdQbkeCt1jSAM7lZqg==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.25.9.tgz", + "integrity": "sha512-tnUA4RsrmflIM6W6RFTLFSXITtl0wKjgpnLgXyowocVPrbYrLUXSBXDgTs8BlbmIzIdlBySRQjINYs2BAkiLtw==", "requires": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/helper-module-transforms": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.24.5.tgz", - "integrity": "sha512-9GxeY8c2d2mdQUP1Dye0ks3VDyIMS98kt/llQ2nUId8IsWqTF0l1LkSX0/uP7l7MCDrzXS009Hyhe2gzTiGW8A==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.26.0.tgz", + "integrity": "sha512-xO+xu6B5K2czEnQye6BHA7DolFFmS3LB7stHZFaOLb1pAwO1HWLS8fXA+eh0A2yIvltPVmx3eNNDBJA2SLHXFw==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-simple-access": "^7.24.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/helper-validator-identifier": "^7.24.5" + "@babel/helper-module-imports": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9", + "@babel/traverse": "^7.25.9" } }, "@babel/helper-optimise-call-expression": { @@ -30703,7 +27412,7 @@ "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.22.5.tgz", "integrity": "sha512-tK14r66JZKiC43p8Ki33yLBVJKlQDFoA8GYN67lWCDCqoL6EMMSuM9b+Iff2jHaM/RRFYl7K+iiru7hbRqNx8Q==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-split-export-declaration": { @@ -30715,19 +27424,19 @@ } }, "@babel/helper-string-parser": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.1.tgz", - "integrity": "sha512-2ofRCjnnA9y+wk8b9IAREroeUP02KHp431N2mhKniy2yKIDKpbrHv9eXwm8cBeWQYcJmzv5qKCu65P47eCF7CQ==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.9.tgz", + "integrity": "sha512-4A/SCr/2KLd5jrtOMFzaKjVtAei3+2r/NChoBNoZ3EyP/+GlhoaEGoWOZUmFmoITP7zOJyHIMm+DYRd8o3PvHA==" }, "@babel/helper-validator-identifier": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.5.tgz", - "integrity": "sha512-3q93SSKX2TWCG30M2G2kwaKeTYgEUp5Snjuj8qm729SObL6nbtUldAi37qbxkD5gg3xnBio+f9nqpSepGZMvxA==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.9.tgz", + "integrity": "sha512-Ed61U6XJc3CVRfkERJWDz4dJwKe7iLmmJsbOGu9wSloNSFttHV0I8g6UAgb7qnK5ly5bGLPd4oXZlxCdANBOWQ==" }, "@babel/helper-validator-option": { - "version": "7.23.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", - "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==" + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.25.9.tgz", + "integrity": "sha512-e/zv1co8pp55dNdEcCynfj9X7nyUKUXoUEwfXqaZt0omVOmDe9oOTdKStH4GmAw6zxMFs50ZayuMfHDKlO7Tfw==" }, "@babel/helper-wrap-function": { "version": "7.24.5", @@ -30740,19 +27449,19 @@ } }, "@babel/helpers": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.24.5.tgz", - "integrity": "sha512-CiQmBMMpMQHwM5m01YnrM6imUG1ebgYJ+fAIW4FZe6m4qHTPaRHti+R8cggAwkdz4oXhtO4/K9JWlh+8hIfR2Q==", + "version": "7.26.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.26.0.tgz", + "integrity": "sha512-tbhNuIxNcVb21pInl3ZSjksLCvgdZy9KwJ8brv993QtIVKJBBkYXz4q4ZbAv31GdnC+R90np23L5FbEBlthAEw==", "requires": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.0" } }, "@babel/highlight": { "version": "7.24.5", "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "dev": true, "requires": { "@babel/helper-validator-identifier": "^7.24.5", "chalk": "^2.4.2", @@ -30761,9 +27470,12 @@ } }, "@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==" + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.26.3.tgz", + "integrity": "sha512-WJ/CvmY8Mea8iDXo6a7RK2wbmJITT5fN3BEkRuFlxVyNx8jOKIIhmC4fSkTcPcf8JyavbBwIe6OpiCOBXt/IcA==", + "requires": { + "@babel/types": "^7.26.3" + } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { "version": "7.24.5", @@ -31508,6 +28220,97 @@ "esutils": "^2.0.2" } }, + "@babel/register": { + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.25.9.tgz", + "integrity": "sha512-8D43jXtGsYmEeDvm4MWHYUpWf8iiXgWYx3fW7E7Wb7Oe6FWqJPl5K6TuFW0dOwNZzEE5rjlaSJYH9JjrUKJszA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "find-cache-dir": "^2.0.0", + "make-dir": "^2.1.0", + "pirates": "^4.0.6", + "source-map-support": "^0.5.16" + }, + "dependencies": { + "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==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.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" + } + }, + "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" + } + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "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 + }, + "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==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + } + }, + "semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true + } + } + }, "@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", @@ -31522,40 +28325,54 @@ } }, "@babel/template": { - "version": "7.24.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.24.0.tgz", - "integrity": "sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==", + "version": "7.25.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.9.tgz", + "integrity": "sha512-9DGttpmPvIxBb/2uwpVo3dqJ+O6RooAFOS+lB+xDqoE2PVCE8nfoHMdZLpfCQRLwvohzXISPZcgxt80xLfsuwg==", "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.25.9", + "@babel/parser": "^7.25.9", + "@babel/types": "^7.25.9" } }, "@babel/traverse": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.24.5.tgz", - "integrity": "sha512-7aaBLeDQ4zYcUFDUD41lJc1fG8+5IU9DaNSJAgal866FGvmD5EbWQgnEC6kO1gGLsX0esNkfnJSndbTXA3r7UA==", - "requires": { - "@babel/code-frame": "^7.24.2", - "@babel/generator": "^7.24.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", - "@babel/parser": "^7.24.5", - "@babel/types": "^7.24.5", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.26.3.tgz", + "integrity": "sha512-yTmc8J+Sj8yLzwr4PD5Xb/WF3bOYu2C2OoSZPzbuqRm4n98XirsbzaX+GloeO376UnSYIYJ4NCanwV5/ugZkwA==", + "requires": { + "@babel/code-frame": "^7.26.2", + "@babel/generator": "^7.26.3", + "@babel/parser": "^7.26.3", + "@babel/template": "^7.25.9", + "@babel/types": "^7.26.3", "debug": "^4.3.1", "globals": "^11.1.0" } }, "@babel/types": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.24.5.tgz", - "integrity": "sha512-6mQNsaLeXTw0nxYUYu+NSa4Hx4BlF1x1x8/PMFbiR+GBSr+2DkECc69b8hgy2frEodNcvPffeH8YfWd3LI6jhQ==", + "version": "7.26.3", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.3.tgz", + "integrity": "sha512-vN5p+1kl59GVKMvTHt55NzzmYVxprfJD+ql7U9NFIfKCBkYE55LYtS+WtPlaYOyzydrKI8Nezd+aZextrd+FMA==", "requires": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", - "to-fast-properties": "^2.0.0" + "@babel/helper-string-parser": "^7.25.9", + "@babel/helper-validator-identifier": "^7.25.9" + } + }, + "@browserstack/ai-sdk-node": { + "version": "1.5.9", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.9.tgz", + "integrity": "sha512-RGLWcxadVEkafFaoeAPnIGxS/C+DtHDYWZ3Sy+P5VvWlDWhN9hZU5lZGMsUZg+XmrnuhodEEaZnJQV9P0IEeMQ==", + "dev": true, + "requires": { + "axios": "^1.7.4", + "uuid": "9.0.1" + }, + "dependencies": { + "uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "dev": true + } } }, "@colors/colors": { @@ -31571,16 +28388,187 @@ "dev": true }, "@es-joy/jsdoccomment": { - "version": "0.22.2", - "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.22.2.tgz", - "integrity": "sha512-pM6WQKcuAtdYoqCsXSvVSu3Ij8K0HY50L8tIheOKHDl0wH1uA4zbP88etY8SIeP16NVCMCTFU+Q2DahSKheGGQ==", + "version": "0.43.1", + "resolved": "https://registry.npmjs.org/@es-joy/jsdoccomment/-/jsdoccomment-0.43.1.tgz", + "integrity": "sha512-I238eDtOolvCuvtxrnqtlBaw0BwdQuYqK7eA6XIonicMdOOOb75mqdIzkGDUbS04+1Di007rgm9snFRNeVrOog==", "dev": true, "requires": { - "comment-parser": "1.3.1", - "esquery": "^1.4.0", - "jsdoc-type-pratt-parser": "~2.2.5" + "@types/eslint": "^8.56.5", + "@types/estree": "^1.0.5", + "@typescript-eslint/types": "^7.2.0", + "comment-parser": "1.4.1", + "esquery": "^1.5.0", + "jsdoc-type-pratt-parser": "~4.0.0" } }, + "@esbuild/aix-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.23.1.tgz", + "integrity": "sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.23.1.tgz", + "integrity": "sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.23.1.tgz", + "integrity": "sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.23.1.tgz", + "integrity": "sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.23.1.tgz", + "integrity": "sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.23.1.tgz", + "integrity": "sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.23.1.tgz", + "integrity": "sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.23.1.tgz", + "integrity": "sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.23.1.tgz", + "integrity": "sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.23.1.tgz", + "integrity": "sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.23.1.tgz", + "integrity": "sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.23.1.tgz", + "integrity": "sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.23.1.tgz", + "integrity": "sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.23.1.tgz", + "integrity": "sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.23.1.tgz", + "integrity": "sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.23.1.tgz", + "integrity": "sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.23.1.tgz", + "integrity": "sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.23.1.tgz", + "integrity": "sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.23.1.tgz", + "integrity": "sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.23.1.tgz", + "integrity": "sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.23.1.tgz", + "integrity": "sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.23.1.tgz", + "integrity": "sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.23.1.tgz", + "integrity": "sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.23.1.tgz", + "integrity": "sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==", + "dev": true, + "optional": true + }, "@eslint/eslintrc": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz", @@ -31758,6 +28746,203 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@inquirer/checkbox": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-3.0.1.tgz", + "integrity": "sha512-0hm2nrToWUdD6/UHnel/UKGdk1//ke5zGUpHIvk5ZWmaKezlGxZkOJXNSWsdxO/rEqTkbB3lNC2J6nBElV2aAQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/confirm": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-4.0.1.tgz", + "integrity": "sha512-46yL28o2NJ9doViqOy0VDcoTzng7rAb6yPQKU7VDLqkmbCaH4JqK4yk4XqlzNWy9PVC5pG1ZUXPBQv+VqnYs2w==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + } + }, + "@inquirer/core": { + "version": "9.2.1", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.2.1.tgz", + "integrity": "sha512-F2VBt7W/mwqEU4bL0RnHNZmC/OxzNx9cOYxHqnXX3MP6ruYvZUZAW9imgN9+h/uBT/oP8Gh888J2OZSbjSeWcg==", + "dev": true, + "requires": { + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.5.5", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "^1.0.0", + "signal-exit": "^4.1.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "dependencies": { + "@types/node": { + "version": "22.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.1.tgz", + "integrity": "sha512-qKgsUwfHZV2WCWLAnVP1JqnpE6Im6h3Y0+fYgMTasNQ7V++CBX5OT1as0g0f+OyubbFqhf6XVNIsmN4IIhEgGQ==", + "dev": true, + "requires": { + "undici-types": "~6.20.0" + } + }, + "signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "undici-types": { + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", + "integrity": "sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==", + "dev": true + } + } + }, + "@inquirer/editor": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-3.0.1.tgz", + "integrity": "sha512-VA96GPFaSOVudjKFraokEEmUQg/Lub6OXvbIEZU1SDCmBzRkHGhxoFAVaF30nyiB4m5cEbDgiI2QRacXZ2hw9Q==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "external-editor": "^3.1.0" + } + }, + "@inquirer/expand": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-3.0.1.tgz", + "integrity": "sha512-ToG8d6RIbnVpbdPdiN7BCxZGiHOTomOX94C2FaT5KOHupV40tKEDozp12res6cMIfRKrXLJyexAZhWVHgbALSQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/figures": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.8.tgz", + "integrity": "sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==", + "dev": true + }, + "@inquirer/input": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-3.0.1.tgz", + "integrity": "sha512-BDuPBmpvi8eMCxqC5iacloWqv+5tQSJlUafYWUe31ow1BVXjW2a5qe3dh4X/Z25Wp22RwvcaLCc2siHobEOfzg==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + } + }, + "@inquirer/number": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-2.0.1.tgz", + "integrity": "sha512-QpR8jPhRjSmlr/mD2cw3IR8HRO7lSVOnqUvQa8scv1Lsr3xoAMMworcYW3J13z3ppjBFBD2ef1Ci6AE5Qn8goQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0" + } + }, + "@inquirer/password": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-3.0.1.tgz", + "integrity": "sha512-haoeEPUisD1NeE2IanLOiFr4wcTXGWrBOyAyPZi1FfLJuXOzNmxCJPgUrGYKVh+Y8hfGJenIfz5Wb/DkE9KkMQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2" + } + }, + "@inquirer/prompts": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-6.0.1.tgz", + "integrity": "sha512-yl43JD/86CIj3Mz5mvvLJqAOfIup7ncxfJ0Btnl0/v5TouVUyeEdcpknfgc+yMevS/48oH9WAkkw93m7otLb/A==", + "dev": true, + "requires": { + "@inquirer/checkbox": "^3.0.1", + "@inquirer/confirm": "^4.0.1", + "@inquirer/editor": "^3.0.1", + "@inquirer/expand": "^3.0.1", + "@inquirer/input": "^3.0.1", + "@inquirer/number": "^2.0.1", + "@inquirer/password": "^3.0.1", + "@inquirer/rawlist": "^3.0.1", + "@inquirer/search": "^2.0.1", + "@inquirer/select": "^3.0.1" + } + }, + "@inquirer/rawlist": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-3.0.1.tgz", + "integrity": "sha512-VgRtFIwZInUzTiPLSfDXK5jLrnpkuSOh1ctfaoygKAdPqjcjKYmGh6sCY1pb0aGnCGsmhUxoqLDUAU0ud+lGXQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/search": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-2.0.1.tgz", + "integrity": "sha512-r5hBKZk3g5MkIzLVoSgE4evypGqtOannnB3PKTG9NRZxyFRKcfzrdxXXPcoJQsxJPzvdSU2Rn7pB7lw0GCmGAg==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/select": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-3.0.1.tgz", + "integrity": "sha512-lUDGUxPhdWMkN/fHy1Lk7pF3nK1fh/gqeyWXmctefhxLYxlDsc7vsPBEpxrfVGDsVdyYJsiJoD4bJ1b623cV1Q==", + "dev": true, + "requires": { + "@inquirer/core": "^9.2.1", + "@inquirer/figures": "^1.0.6", + "@inquirer/type": "^2.0.0", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-2.0.0.tgz", + "integrity": "sha512-XvJRx+2KR3YXyYtPUUy+qd9i7p+GO9Ko6VIIpWlBrpWwXDv8WLFeHTxz35CfQFUiBMLXlGHhGzys7lqit9gWag==", + "dev": true, + "requires": { + "mute-stream": "^1.0.0" + } + }, "@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -31941,9 +29126,9 @@ } }, "@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", - "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.0.tgz", + "integrity": "sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==" }, "@jridgewell/trace-mapping": { "version": "0.3.25", @@ -31954,15 +29139,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "@ljharb/through": { - "version": "2.3.13", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.13.tgz", - "integrity": "sha512-/gKJun8NNiWGZJkGzI/Ragc53cOdcLNdzjLaIa+GEjguQs0ulsurx8WN0jijdK9yPqDvziX995sMRLyLt1uZMQ==", - "dev": true, - "requires": { - "call-bind": "^1.0.7" - } - }, "@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", @@ -32011,6 +29187,12 @@ "dev": true, "optional": true }, + "@pkgr/core": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@pkgr/core/-/core-0.1.1.tgz", + "integrity": "sha512-cq8o4cWH0ibXh9VGi5P20Tu9XF/0fFXl9EUinr9QfTM7a7p0oTA4iJRCQWppXR1Pg8dSM0UCItCkPwsk9qWWYA==", + "dev": true + }, "@polka/url": { "version": "1.0.0-next.25", "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.25.tgz", @@ -32043,6 +29225,15 @@ "yargs": "17.7.2" }, "dependencies": { + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -32060,16 +29251,28 @@ } } }, + "@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "@sec-ant/readable-stream": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@sec-ant/readable-stream/-/readable-stream-0.4.1.tgz", + "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", + "dev": true + }, "@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", "integrity": "sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==", "dev": true }, - "@sindresorhus/is": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", - "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "@sindresorhus/merge-streams": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", + "integrity": "sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==", "dev": true }, "@sinonjs/commons": { @@ -32102,9 +29305,9 @@ } }, "@sinonjs/text-encoding": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", - "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.3.tgz", + "integrity": "sha512-DE427ROAphMQzU4ENbliGYrBSYPXF+TtLg9S8vzeA+OF4ZKzoDdzfL8sxuMUGS/lgRhM6j1URSk9ghf7Xo1tyA==", "dev": true }, "@socket.io/component-emitter": { @@ -32113,47 +29316,12 @@ "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", "dev": true }, - "@szmarczak/http-timer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", - "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.1" - } - }, - "@tootallnate/once": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", - "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", - "dev": true, - "optional": true, - "peer": true - }, "@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", "dev": true }, - "@types/aria-query": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/@types/aria-query/-/aria-query-5.0.4.tgz", - "integrity": "sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==", - "dev": true - }, - "@types/cacheable-request": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.3.tgz", - "integrity": "sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "^3.1.4", - "@types/node": "*", - "@types/responselike": "^1.0.0" - } - }, "@types/cookie": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", @@ -32222,16 +29390,6 @@ "integrity": "sha512-W6hyZux6TrtKfF2I9XNLVcsFr4xRr0T+S6hrJ9nDkhA2vzsFPIEAbnY4vgb6v2yKXQ9MJVcbLsARNlMfg4EVtQ==", "dev": true }, - "@types/glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==", - "dev": true, - "requires": { - "@types/minimatch": "^5.1.2", - "@types/node": "*" - } - }, "@types/hast": { "version": "2.3.10", "resolved": "https://registry.npmjs.org/@types/hast/-/hast-2.3.10.tgz", @@ -32241,12 +29399,6 @@ "@types/unist": "^2" } }, - "@types/http-cache-semantics": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", - "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", - "dev": true - }, "@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -32283,15 +29435,6 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, "@types/mdast": { "version": "3.0.15", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-3.0.15.tgz", @@ -32301,12 +29444,6 @@ "@types/unist": "^2" } }, - "@types/minimatch": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", - "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", - "dev": true - }, "@types/mocha": { "version": "10.0.6", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-10.0.6.tgz", @@ -32319,6 +29456,15 @@ "integrity": "sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==", "dev": true }, + "@types/mute-stream": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/@types/mute-stream/-/mute-stream-0.0.4.tgz", + "integrity": "sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/node": { "version": "20.12.12", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", @@ -32340,14 +29486,11 @@ "integrity": "sha512-SuT16Q1K51EAVPz1K29DJ/sXjhSQ0zjvsypYJ6tlwVsRV9jwW5Adq2ch8Dq8kDBCkYnELS7N7VNCSB5nC56t/g==", "dev": true }, - "@types/responselike": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", - "integrity": "sha512-H/+L+UkTV33uf49PH5pCAUBVPNj2nDBXTN+qS1dOwyyg24l3CcicicCA7ca+HMvJBZcFgl5r8e+RR6elsb4Lyw==", - "dev": true, - "requires": { - "@types/node": "*" - } + "@types/sinonjs__fake-timers": { + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.5.tgz", + "integrity": "sha512-mQkU2jY8jJEF7YHjHvsQO8+3ughTL1mcnn96igfhONmR+fUPSKIkefQYpSe8bsly2Ep7oQbn/6VG5/9/0qcArQ==", + "dev": true }, "@types/stack-utils": { "version": "2.0.3", @@ -32367,12 +29510,6 @@ "integrity": "sha512-6WaYesThRMCl19iryMYP7/x2OVgCtbIVflDGFpWnb9irXI3UjYE4AzmYuiUKY1AJstGijoY+MgUszMgRxIYTYw==", "dev": true }, - "@types/ua-parser-js": { - "version": "0.7.39", - "resolved": "https://registry.npmjs.org/@types/ua-parser-js/-/ua-parser-js-0.7.39.tgz", - "integrity": "sha512-P/oDfpofrdtF5xw433SPALpdSchtJmY7nsJItf8h3KXqOslkbySh8zq4dSWXH2oTjRvJ5PczVEoCZPow6GicLg==", - "dev": true - }, "@types/unist": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.10.tgz", @@ -32395,19 +29532,25 @@ "integrity": "sha512-113D3mDkZDjo+EeUEHCFy0qniNc1ZpecGiAU7WSo7YDoSzolZIQKpYFHrPpjkB2nuyahcKfrmLXeQlh7gqJYdw==", "dev": true }, + "@types/wrap-ansi": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/wrap-ansi/-/wrap-ansi-3.0.0.tgz", + "integrity": "sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==", + "dev": true + }, "@types/ws": { - "version": "8.5.10", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz", - "integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==", + "version": "8.5.13", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz", + "integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==", "dev": true, "requires": { "@types/node": "*" } }, "@types/yargs": { - "version": "17.0.32", - "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", - "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==", + "version": "17.0.33", + "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.33.tgz", + "integrity": "sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==", "dev": true, "requires": { "@types/yargs-parser": "*" @@ -32429,6 +29572,12 @@ "@types/node": "*" } }, + "@typescript-eslint/types": { + "version": "7.15.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.15.0.tgz", + "integrity": "sha512-aV1+B1+ySXbQH0pLK0rx66I3IkiZNidYobyfn0WFsdGhSXw+P3YOqeTq5GED458SfB24tg+ux3S+9g118hjlTw==", + "dev": true + }, "@videojs/http-streaming": { "version": "2.16.2", "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.2.tgz", @@ -32467,15 +29616,24 @@ "is-function": "^1.0.1" } }, + "@vitest/pretty-format": { + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.8.tgz", + "integrity": "sha512-9HiSZ9zpqNLKlbIDRWOnAWqgcA7xu+8YxXSekhr0Ykab7PAYFkhkwoqVArPOtJhPmYeE2YHgKZlj3CP36z2AJQ==", + "dev": true, + "requires": { + "tinyrainbow": "^1.2.0" + } + }, "@vitest/snapshot": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.0.tgz", - "integrity": "sha512-+Hx43f8Chus+DCmygqqfetcAZrDJwvTj0ymqjQq4CvmpKFSTVteEOBzCusu1x2tt4OJcvBflyHUE0DZSLgEMtQ==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.8.tgz", + "integrity": "sha512-20T7xRFbmnkfcmgVEz+z3AU/3b0cEzZOt/zmnvZEctg64/QZbSDJEVm9fLnnlSi74KibmRsO9/Qabi+t0vCRPg==", "dev": true, "requires": { - "magic-string": "^0.30.5", - "pathe": "^1.1.1", - "pretty-format": "^29.7.0" + "@vitest/pretty-format": "2.1.8", + "magic-string": "^0.30.12", + "pathe": "^1.1.2" } }, "@vue/compiler-core": { @@ -32540,1160 +29698,309 @@ "optional": true }, "@wdio/browserstack-service": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-8.36.1.tgz", - "integrity": "sha512-3xCQaKk7FUjTzDPYyQlUrLpfTlHz+WlSx9ppUG5ZNaJG08Gqu0nWyUCcJCdQXVpKeYTduX7DzIBIb7Efp/3+4g==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.4.1.tgz", + "integrity": "sha512-gRYh7xkQqE9/080S3R5Dz2zw4kQQzMgRO8lyf15R91TVJQkwhrXG6qeo1c3Ri1n7l34C6sJTlOf+ulQJXMUk9Q==", "dev": true, "requires": { + "@browserstack/ai-sdk-node": "1.5.9", "@percy/appium-app": "^2.0.1", "@percy/selenium-webdriver": "^2.0.3", "@types/gitconfiglocal": "^2.0.1", - "@wdio/logger": "8.28.0", - "@wdio/reporter": "8.36.1", - "@wdio/types": "8.36.1", + "@wdio/logger": "9.1.3", + "@wdio/reporter": "9.2.14", + "@wdio/types": "9.2.2", "browserstack-local": "^1.5.1", "chalk": "^5.3.0", "csv-writer": "^1.6.0", "formdata-node": "5.0.1", "git-repo-info": "^2.1.1", "gitconfiglocal": "^2.1.0", - "got": "^12.6.1", - "uuid": "^9.0.0", - "webdriverio": "8.36.1", + "uuid": "^10.0.0", + "webdriverio": "9.4.1", "winston-transport": "^4.5.0", "yauzl": "^3.0.0" }, "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "@wdio/reporter": { + "version": "9.2.14", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.2.14.tgz", + "integrity": "sha512-njOqa9+w5zc9AY4fsUmW46EhZ2nzMFRXZPCmwnGZuYk81L3slVfAFhKk1wSTRhSbghjYAZndt9Yo3c4wFLh6Lg==", "dev": true, "requires": { - "balanced-match": "^1.0.0" + "@types/node": "^20.1.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "diff": "^7.0.0", + "object-inspect": "^1.12.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "@types/node": "^20.1.0" } }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, "chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "debug": "4" - } - }, - "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } - } - }, - "devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true - }, - "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", - "dev": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - } - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "diff": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-7.0.0.tgz", + "integrity": "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw==", "dev": true - }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", - "dev": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, "@wdio/cli": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.36.1.tgz", - "integrity": "sha512-LZBZiwcvvv5P0HuRXt8IV09UiFT5dnDr1Ag5u2roJL2D7l8wDHHa70PXw9MmlbrnyFCUN3hO7FQVUi9MAsDbDQ==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.4.1.tgz", + "integrity": "sha512-GDyAer63WDsr2ckXmrpUyAcIZFd3pCRIpi85rL1ZjnWthRy/UtwY0EHPMDuSeUEJ28iYwW3esKgq2ZKlsdbMeA==", "dev": true, "requires": { "@types/node": "^20.1.1", - "@vitest/snapshot": "^1.2.1", - "@wdio/config": "8.36.1", - "@wdio/globals": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", + "@vitest/snapshot": "^2.1.1", + "@wdio/config": "9.2.8", + "@wdio/globals": "9.4.1", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", - "chokidar": "^3.5.3", - "cli-spinners": "^2.9.0", + "chokidar": "^4.0.0", + "cli-spinners": "^3.0.0", "dotenv": "^16.3.1", "ejs": "^3.1.9", - "execa": "^8.0.1", + "execa": "^9.2.0", "import-meta-resolve": "^4.0.0", - "inquirer": "9.2.12", + "inquirer": "^11.0.1", "lodash.flattendeep": "^4.4.0", "lodash.pickby": "^4.6.0", "lodash.union": "^4.6.0", - "read-pkg-up": "10.0.0", + "read-pkg-up": "^10.0.0", "recursive-readdir": "^2.2.3", - "webdriverio": "8.36.1", + "tsx": "^4.7.2", + "webdriverio": "9.4.1", "yargs": "^17.7.2" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - }, - "dependencies": { - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } - } - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "requires": { - "debug": "^4.3.4" - } - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true - }, - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true - }, - "chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - }, - "dependencies": { - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - } - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "debug": "4" - } - }, - "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - } + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", + "dev": true, + "requires": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } }, - "devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, - "optional": true, - "peer": true + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } }, - "execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "requires": { - "cross-spawn": "^7.0.3", - "get-stream": "^8.0.1", - "human-signals": "^5.0.0", - "is-stream": "^3.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^5.1.0", - "onetime": "^6.0.0", - "signal-exit": "^4.1.0", - "strip-final-newline": "^3.0.0" + "@types/node": "^20.1.0" } }, - "get-stream": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", - "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", - "dev": true + "@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } }, - "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "agent-base": "^7.0.2", - "debug": "4" + "debug": "^4.3.4" } }, - "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "chokidar": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", + "integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "dev": true, - "optional": true, - "peer": true, "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } + "readdirp": "^4.0.1" } }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true - }, - "mimic-fn": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", - "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "execa": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-9.4.0.tgz", + "integrity": "sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "@sindresorhus/merge-streams": "^4.0.0", + "cross-spawn": "^7.0.3", + "figures": "^6.1.0", + "get-stream": "^9.0.0", + "human-signals": "^8.0.0", + "is-plain-obj": "^4.1.0", + "is-stream": "^4.0.1", + "npm-run-path": "^6.0.0", + "pretty-ms": "^9.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.0" } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "get-stream": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-9.0.1.tgz", + "integrity": "sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==", "dev": true, "requires": { - "whatwg-url": "^5.0.0" + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" } }, - "npm-run-path": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", - "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "requires": { - "path-key": "^4.0.0" + "agent-base": "^7.0.2", + "debug": "4" } }, - "onetime": { + "is-stream": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz", + "integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==", + "dev": true + }, + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true + }, + "npm-run-path": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", - "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz", + "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==", "dev": true, "requires": { - "mimic-fn": "^4.0.0" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" } }, + "parse-ms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-4.0.0.tgz", + "integrity": "sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==", + "dev": true + }, "path-key": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", "dev": true }, + "pretty-ms": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-9.1.0.tgz", + "integrity": "sha512-o1piW0n3tgKIKCwk2vpM/vOV13zjJzvP37Ioze54YlTHE06m4tjEbzg9WsKkvTuyYln2DHjo5pY4qrZGI0otpw==", + "dev": true, + "requires": { + "parse-ms": "^4.0.0" + } + }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - } - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } + "socks-proxy-agent": "^8.0.2" } }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } + "readdirp": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.2.tgz", + "integrity": "sha512-yDMz9g+VaZkqBYS/ozoBJwaBhTbZo3UNYQHNRw1D3UFQB8oHB4uS/tAODO+ZLjGWmUbKnIlOWO+aaIiAxrUWHA==", + "dev": true }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true }, "signal-exit": { "version": "4.1.0", @@ -33701,68 +30008,18 @@ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", "dev": true }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true - }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} - }, "yargs": { "version": "17.7.2", "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", @@ -33777,17 +30034,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -33812,571 +30058,154 @@ } }, "@wdio/config": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.36.1.tgz", - "integrity": "sha512-yCENnym0CrYuLKMJ3fv00WkjCR8QpPqVohGBkq5FvZOZpVJEpoG86Q8l4HtyRnd6ggMTKCA1vTQ/myhbPmZmaQ==", + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.2.8.tgz", + "integrity": "sha512-EGMmBPGJbz6RmgMjebRWkWu3fGyeTIRcusF4UA4f2tiUEKY8nbzUO/ZyDjVQNR+YVB40q0jcqAqpszYRrIzzeg==", "dev": true, "requires": { - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", "decamelize": "^6.0.0", - "deepmerge-ts": "^5.0.0", + "deepmerge-ts": "^7.0.3", "glob": "^10.2.2", "import-meta-resolve": "^4.0.0" - } - }, - "@wdio/globals": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.36.1.tgz", - "integrity": "sha512-Qpj6gZCRNxqdVkTwYyi4JdeYO4tLSUj3Ti6yxO0v9A4IRaKW1tS29KUcGgjL9CFSBKAOi2zRY8vvFz1u6ewxtQ==", - "dev": true, - "requires": { - "expect-webdriverio": "^4.11.2", - "webdriverio": "8.36.1" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "optional": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "optional": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "optional": true - }, - "chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, - "optional": true, "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, - "optional": true, "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "@types/node": "^20.1.0" } }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" + "@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" } }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "debug": "4" - } - }, - "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "debug": "^4.3.4" } }, - "devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true, - "optional": true + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", + "dev": true }, "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, - "optional": true, "requires": { "agent-base": "^7.0.2", "debug": "4" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true - }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } + "dev": true }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, - "optional": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - } - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.2.0" + "socks-proxy-agent": "^8.0.2" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true - }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true }, - "webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, - "optional": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "optional": true, - "requires": {} - }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, - "optional": true, "requires": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -34386,41 +30215,105 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } + } + } + }, + "@wdio/globals": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.4.1.tgz", + "integrity": "sha512-CTVAVJ7iFyT54XF9iRmNvsDB+WSHoztJPG9XPL/mHzQ2LYfSyUR8E/j+3iHbTx3v/qRNucgPcGwhxiuY2RcaDg==", + "dev": true, + "requires": { + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.4.1" + }, + "dependencies": { + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "optional": true, + "peer": true + }, + "expect-webdriverio": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.5.tgz", + "integrity": "sha512-h04OGd7ZksVj8bgv3bYdjFpmJuKeCnyRrBmpMxYpMDmYSspxg9vsSr0kD5p9oOM16bX0ZXEVXr42RbI2hoLpTw==", "dev": true, "optional": true, "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" } } } }, "@wdio/local-runner": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-8.36.1.tgz", - "integrity": "sha512-FYsTzbNGRnrniOsLWrZO7+DLecAS9W75AIzFZQVQxruiDFkGmKY5OV6gsuvMlasaqAQXW1s+w29bqrLY4DxdEw==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.4.1.tgz", + "integrity": "sha512-MM5VM0V7zvajICr6eNROjkppRhGNpdV4nU5hrgSap92nou8G+zBgLxJ45P5BzLw67KQTOEa1E32b/zCBEkO+0g==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/repl": "8.24.12", - "@wdio/runner": "8.36.1", - "@wdio/types": "8.36.1", + "@wdio/logger": "9.1.3", + "@wdio/repl": "9.0.8", + "@wdio/runner": "9.4.1", + "@wdio/types": "9.2.2", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } } }, "@wdio/logger": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", - "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "version": "8.38.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.38.0.tgz", + "integrity": "sha512-kcHL86RmNbcQP+Gq/vQUGlArfU6IIcbbnNp32rRIraitomZow+iEoc519rdQmSVusDozMS5DZthkgDdxK+vz6Q==", "dev": true, "requires": { "chalk": "^5.1.2", @@ -34449,18 +30342,38 @@ "@wdio/types": "8.36.1", "@wdio/utils": "8.36.1", "mocha": "^10.0.0" + }, + "dependencies": { + "@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } } }, "@wdio/protocols": { - "version": "8.32.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-8.32.0.tgz", - "integrity": "sha512-inLJRrtIGdTz/YPbcsvpSvPlYQFTVtF3OYBwAXhG2FiP1ZwE1CQNLP/xgRGye1ymdGCypGkexRqIx3KBGm801Q==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.2.2.tgz", + "integrity": "sha512-0GMUSHCbYm+J+rnRU6XPtaUgVCRICsiH6W5zCXpePm3wLlbmg/mvZ+4OnNErssbpIOulZuAmC2jNmut2AEfWSw==", "dev": true }, "@wdio/repl": { - "version": "8.24.12", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-8.24.12.tgz", - "integrity": "sha512-321F3sWafnlw93uRTSjEBVuvWCxTkWNDs7ektQS15drrroL3TMeFOynu4rDrIz0jXD9Vas0HCD2Tq/P0uxFLdw==", + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.8.tgz", + "integrity": "sha512-3iubjl4JX5zD21aFxZwQghqC3lgu+mSs8c3NaiYYNCC+IT5cI/8QuKlgh9s59bu+N3gG988jqMJeCYlKuUv/iw==", "dev": true, "requires": { "@types/node": "^20.1.0" @@ -34477,540 +30390,191 @@ "@wdio/types": "8.36.1", "diff": "^5.0.0", "object-inspect": "^1.12.0" + }, + "dependencies": { + "@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } } }, "@wdio/runner": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-8.36.1.tgz", - "integrity": "sha512-bLkxQ46MLEbzIf30adl2nyz8kxED/V0IjcQASm0VKfNmsG8LOf7iOIz+udOF4GkMoF++5JuONA5abUsyLvwatg==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.4.1.tgz", + "integrity": "sha512-WUpYamLafv+GUofGqvA2HO1kSVnwLyPO5ujq3TS/YaJLnUDOj4bjHiRo5+KDbKw1xhqjW0GjbfDupVU5LeL9iw==", "dev": true, "requires": { "@types/node": "^20.11.28", - "@wdio/config": "8.36.1", - "@wdio/globals": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "deepmerge-ts": "^5.1.0", - "expect-webdriverio": "^4.12.0", - "gaze": "^1.1.3", - "webdriver": "8.36.1", - "webdriverio": "8.36.1" + "@wdio/config": "9.2.8", + "@wdio/globals": "9.4.1", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", + "webdriver": "9.4.1", + "webdriverio": "9.4.1" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, "requires": { - "debug": "^4.3.4" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" + "@types/node": "^20.1.0" } }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" + "@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "debug": "^4.3.4" } }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "requires": { - "node-fetch": "^2.6.12" - } + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", + "dev": true }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "expect-webdriverio": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.5.tgz", + "integrity": "sha512-h04OGd7ZksVj8bgv3bYdjFpmJuKeCnyRrBmpMxYpMDmYSspxg9vsSr0kD5p9oOM16bX0ZXEVXr42RbI2hoLpTw==", "dev": true, - "optional": true, - "peer": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "debug": "4" - } - }, - "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" } }, - "devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true - }, "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "4" } }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true - }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, "lru-cache": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { "agent-base": "^7.0.2", "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", + "pac-proxy-agent": "^7.0.1", "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - } - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "requires": { - "safe-buffer": "~5.2.0" + "socks-proxy-agent": "^8.0.2" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "requires": {} - }, "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { "cliui": "^8.0.1", @@ -35021,17 +30585,6 @@ "y18n": "^5.0.5", "yargs-parser": "^21.1.1" } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } } } }, @@ -35084,6 +30637,26 @@ "safaridriver": "^0.1.0", "split2": "^4.2.0", "wait-port": "^1.0.4" + }, + "dependencies": { + "@wdio/logger": { + "version": "8.28.0", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", + "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + } + }, + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + } } }, "@webassemblyjs/ast": { @@ -35250,6 +30823,12 @@ "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", "dev": true }, + "@zip.js/zip.js": { + "version": "2.7.48", + "resolved": "https://registry.npmjs.org/@zip.js/zip.js/-/zip.js-2.7.48.tgz", + "integrity": "sha512-J7cliimZ2snAbr0IhLx2U8BwfA1pKucahKzTpFtYq4hEgKxwvFJcIjCIVNPwQpfVab7iVP+AKmoH1gidBlyhiQ==", + "dev": true + }, "abbrev": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", @@ -35392,6 +30971,7 @@ "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, "requires": { "color-convert": "^1.9.0" } @@ -35421,82 +31001,117 @@ } }, "archiver": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", - "integrity": "sha512-+25nxyyznAXF7Nef3y0EbBeqmGZgeN/BxHX29Rs39djAfaFalmQ89SE6CWyDCHzGL0yt/ycBtNOmGTW0FyGWNw==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "dev": true, "requires": { - "archiver-utils": "^2.1.0", + "archiver-utils": "^5.0.2", "async": "^3.2.4", - "buffer-crc32": "^0.2.1", - "readable-stream": "^3.6.0", + "buffer-crc32": "^1.0.0", + "readable-stream": "^4.0.0", "readdir-glob": "^1.1.2", - "tar-stream": "^2.2.0", - "zip-stream": "^4.1.0" + "tar-stream": "^3.0.0", + "zip-stream": "^6.0.1" }, "dependencies": { "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", + "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==", + "dev": true + }, + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "buffer-crc32": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", + "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", "dev": true }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, - "tar-stream": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", - "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" + "safe-buffer": "~5.2.0" } } } }, "archiver-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-2.1.0.tgz", - "integrity": "sha512-bEL/yUb/fNNiNTuUz979Z0Yg5L+LzLxGJz8x79lYmR54fmTIb6ob/hNQgkQnIUDWIFjZVQwl9Xs356I6BAMHfw==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "dev": true, "requires": { - "glob": "^7.1.4", + "glob": "^10.0.0", "graceful-fs": "^4.2.0", + "is-stream": "^2.0.1", "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", + "lodash": "^4.17.15", "normalize-path": "^3.0.0", - "readable-stream": "^2.0.0" + "readable-stream": "^4.0.0" }, "dependencies": { - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", + "dev": true, + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" } } } @@ -35507,6 +31122,12 @@ "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", "dev": true }, + "are-docs-informative": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/are-docs-informative/-/are-docs-informative-0.0.2.tgz", + "integrity": "sha512-ixiS0nLNNG5jNQzgZJNoUpBKdo9yTYZMGJ+QgT2jmjR7G7+QHRCc4v6LQ3NgE7EBJq+o0ams3waJwkrlBom8Ig==", + "dev": true + }, "argparse": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", @@ -35669,6 +31290,14 @@ "default-compare": "^1.0.0", "get-value": "^2.0.6", "kind-of": "^5.0.2" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "array-uniq": { @@ -35786,9 +31415,9 @@ }, "dependencies": { "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true } } @@ -35871,160 +31500,35 @@ "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "dev": true }, - "b4a": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", - "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha512-VhumSSbBqDTP8p2ZLKj40UjBCV4+v8bUSEpUb4KjRgWk9pbqGF4REFj6KEagidb2f/M6AzC0EmFyDNGaw9OCzg==", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha512-KKNVtd6pCYgPIKU4cp2733HWYCpplQhddZLBUryaAHou723x+FRzQ5Df824Fj+IyyuiQTRoub4SnIFfIcrp70g==", - "dev": true - } - } - }, - "babel-core": { - "version": "6.26.3", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.3.tgz", - "integrity": "sha512-6jyFLuDmeidKmUEb3NM+/yawG0M2bDZ9Z1qbZP59cyHLz8kYGKYwpJP0UwUKKUiTRNvxfLesJnTedqczP7cTDA==", + "axios": { + "version": "1.7.9", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", + "integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "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" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" }, "dependencies": { - "convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "form-data": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz", + "integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==", "dev": true, "requires": { - "ms": "2.0.0" + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" } - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha512-4xrs1aW+6N5DalkqSVA8fxh458CXvR99WU8WLKmq4v8eWAL86Xo3BVqyd3SkA9wEVjCMqyvvRRkshAdOnBp5rw==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "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": "sha512-Mke0DA0QjUWuJlhsE0ZPPhYiJkRap642SmI/4ztCFaUs6V2AiH1sfecc+57NgaryfAA2VR3v6O+CSjC1jZJKOA==", - "dev": true } } }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha512-n7pFrqQm44TCYvrCDb0MqabAF+JUBq+ijBvNMUxpkLjJaAu32faIexewMumrH5KLLJ1HDyT0PTEqRyAe/GwwuQ==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } + "b4a": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.6.tgz", + "integrity": "sha512-5Tk1HLk6b6ctmjIkAcU/Ujv/1WqiDl0F0JdRCR80VsOcUlHcu7pWeWRlOqQLHfDEsVx9YH/aif5AG4ehoCtTmg==", + "dev": true }, "babel-loader": { "version": "8.3.0", @@ -36038,15 +31542,6 @@ "schema-utils": "^2.6.5" } }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha512-Bl3ZiA+LjqaMtNYopA9TYE9HP1tQ+E5dLxE0XrAzcIJeK2UqF0/EaqXwBn9esd4UmTfEab+P+UYQ1GnioFIb/w==", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, "babel-plugin-istanbul": { "version": "6.1.1", "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", @@ -36087,132 +31582,6 @@ "@babel/helper-define-polyfill-provider": "^0.6.2" } }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha512-veliHlHX06wjaeY8xNITbveXSiI+ASFnOqvne/LaIJIqOWi2Ogmj91KOugEz/hoh/fwMhXNBJPCv8Xaz5CyM4A==", - "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.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - }, - "dependencies": { - "core-js": { - "version": "2.6.12", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.12.tgz", - "integrity": "sha512-Kb2wC0fvsWfQrgk8HU5lW6U/Lcs8+9aaYcy4ZFc6DDlo4nZ7n70dEgE5rtR0oG6ufKDUnrwfWL1mXR5ljDatrQ==", - "dev": true - }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - } - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha512-PCOcLFW7/eazGUKIoqH97sO9A2UYMahsn/yRQ7uOk37iutwjq7ODtcTNF+iFDSHNfkctqsLRjLP7URnOx0T1fg==", - "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": "sha512-iSxeXx7apsjCHe9c7n8VtRXGzI2Bk1rBSOJgCCjfyXb6v1aCqE1KSEpq/8SXuVN8Ka/Rh1WDTF0MDzkvTA4MIA==", - "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": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", - "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": "sha512-lxrWP8ejsq+7E3nNjwYmUBMAgjMTZoTI+sdBOpvNyijeDLa29LUn9QaoXAHv4+Z578hbmHHJKZknzxVtvo77og==", - "dev": true - } - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, "bach": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/bach/-/bach-1.2.0.tgz", @@ -36414,30 +31783,6 @@ "file-uri-to-path": "1.0.0" } }, - "bl": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", - "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", - "dev": true, - "requires": { - "buffer": "^5.5.0", - "inherits": "^2.0.4", - "readable-stream": "^3.4.0" - }, - "dependencies": { - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - } - } - }, "bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", @@ -36480,9 +31825,9 @@ } }, "body-parser": { - "version": "1.20.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", - "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz", + "integrity": "sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==", "requires": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -36492,7 +31837,7 @@ "http-errors": "2.0.0", "iconv-lite": "0.4.24", "on-finished": "2.4.1", - "qs": "6.11.0", + "qs": "6.13.0", "raw-body": "2.5.2", "type-is": "~1.6.18", "unpipe": "1.0.0" @@ -36530,12 +31875,12 @@ } }, "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "dev": true, "requires": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" } }, "browser-stdout": { @@ -36545,14 +31890,14 @@ "dev": true }, "browserslist": { - "version": "4.23.0", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.0.tgz", - "integrity": "sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==", + "version": "4.24.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.24.2.tgz", + "integrity": "sha512-ZIc+Q62revdMcqC6aChtW4jz3My3klmCO1fEmINZY/8J3EpBg5/A/D0AKmBveUh6pgoeycoMkVMko84tuYS+Gg==", "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", - "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "caniuse-lite": "^1.0.30001669", + "electron-to-chromium": "^1.5.41", + "node-releases": "^2.0.18", + "update-browserslist-db": "^1.1.1" } }, "browserstack": { @@ -36772,35 +32117,6 @@ "unset-value": "^1.0.0" } }, - "cacheable-lookup": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", - "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", - "dev": true - }, - "cacheable-request": { - "version": "10.2.14", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", - "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", - "dev": true, - "requires": { - "@types/http-cache-semantics": "^4.0.2", - "get-stream": "^6.0.1", - "http-cache-semantics": "^4.1.1", - "keyv": "^4.5.3", - "mimic-response": "^4.0.0", - "normalize-url": "^8.0.0", - "responselike": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - } - } - }, "call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -36832,9 +32148,9 @@ "dev": true }, "caniuse-lite": { - "version": "1.0.30001620", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001620.tgz", - "integrity": "sha512-WJvYsOjd1/BYUY6SNGUosK9DUidBPDTnOARHp3fSmFO1ekdxaY6nKRttEVrfMmYi80ctS0kz1wiWmm14fVc3ew==" + "version": "1.0.30001686", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001686.tgz", + "integrity": "sha512-Y7deg0Aergpa24M3qLC5xjNklnKnhsmSyR/V89dLZ1n0ucJIFNs7PgR2Yfa/Zf6W79SbBicgtGxZr2juHkEUIA==" }, "caseless": { "version": "0.12.0", @@ -36876,6 +32192,7 @@ "version": "2.4.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, "requires": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -36915,6 +32232,50 @@ "get-func-name": "^2.0.2" } }, + "cheerio": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.0.0.tgz", + "integrity": "sha512-quS9HgjQpdaXOvsZz82Oz7uxtXiy6UIsIQcpBj7HRw2M63Skasm9qlDocAM7jNuaxdhpPU7c4kJN+gA5MCu4ww==", + "dev": true, + "requires": { + "cheerio-select": "^2.1.0", + "dom-serializer": "^2.0.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "encoding-sniffer": "^0.2.0", + "htmlparser2": "^9.1.0", + "parse5": "^7.1.2", + "parse5-htmlparser2-tree-adapter": "^7.0.0", + "parse5-parser-stream": "^7.1.2", + "undici": "^6.19.5", + "whatwg-mimetype": "^4.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "requires": { + "entities": "^4.5.0" + } + } + } + }, + "cheerio-select": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz", + "integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==", + "dev": true, + "requires": { + "boolbase": "^1.0.0", + "css-select": "^5.1.0", + "css-what": "^6.1.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1" + } + }, "chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -36931,47 +32292,12 @@ "readdirp": "~3.6.0" } }, - "chownr": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", - "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", - "dev": true - }, - "chrome-launcher": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-0.15.2.tgz", - "integrity": "sha512-zdLEwNo3aUVzIhKhTtXfxhdvZhUghrnmkvcAq2NoDd+LeOHKf03H5jwZ8T/STsAlzyALkBVK552iaG1fGf1xVQ==", - "dev": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^1.0.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true - } - } - }, "chrome-trace-event": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", - "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", + "integrity": "sha512-rNjApaLzuwaOTjCiT8lSDdGN1APCiqkChLMJxJPWLunPAt5fy8xgU9/jNOchV84wfIxrA0lRQB7oCT8jrn/wrQ==", "dev": true }, - "chromium-bidi": { - "version": "0.4.16", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.16.tgz", - "integrity": "sha512-7ZbXdWERxRxSwo3txsBjjmc/NLxqb1Bk30mRb0BMS4YIaiV6zvKZqL/UAH+DdqcDYayDWk2n/y8klkBDODrPvA==", - "dev": true, - "requires": { - "mitt": "3.0.0" - } - }, "ci-info": { "version": "3.9.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", @@ -37027,9 +32353,9 @@ } }, "cli-spinners": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", - "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-3.1.0.tgz", + "integrity": "sha512-2MH0N34TpDAs9ROPVkZJfBWFoYfv4zfkJF14PBHY4v/qRY75SLcm4WaEKNCLScsXieosa/tY/+slJ+BDswJxHQ==", "dev": true }, "cli-width": { @@ -37107,20 +32433,25 @@ "integrity": "sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==", "dev": true }, - "clone-response": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.3.tgz", - "integrity": "sha512-ROoL94jJH2dUVML2Y/5PEDNaSHgeOdSDicUyS7izcF63G6sTc/FTjLub4b8Il9S8S0beOfYt0TaA5qvFK+w0wA==", + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", "dev": true, "requires": { - "mimic-response": "^1.0.0" + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" }, "dependencies": { - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", - "dev": true + "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" + } } } }, @@ -37172,6 +32503,7 @@ "version": "1.9.3", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, "requires": { "color-name": "1.1.3" } @@ -37179,7 +32511,8 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true }, "color-support": { "version": "1.1.3", @@ -37215,9 +32548,9 @@ "dev": true }, "comment-parser": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.3.1.tgz", - "integrity": "sha512-B52sN2VNghyq5ofvUsqZjmk6YkihBX5vMSChmSK9v4ShjKf3Vk5Xcmgpw4o+iIgtrnM/u5FiMpz9VKb8lpBveA==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true }, "commondir": { @@ -37233,26 +32566,54 @@ "dev": true }, "compress-commons": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-4.1.2.tgz", - "integrity": "sha512-D3uMHtGc/fcO1Gt1/L7i1e33VOvD4A9hfQLP+6ewd+BvG/gQ84Yh4oftEhAdjSMgBgwGL+jsppT7JYNpo6MHHg==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "requires": { - "buffer-crc32": "^0.2.13", - "crc32-stream": "^4.0.2", + "crc-32": "^1.2.0", + "crc32-stream": "^6.0.0", + "is-stream": "^2.0.1", "normalize-path": "^3.0.0", - "readable-stream": "^3.6.0" + "readable-stream": "^4.0.0" }, "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true + }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "requires": { + "safe-buffer": "~5.2.0" } } } @@ -37390,9 +32751,9 @@ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" }, "cookie": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", - "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.1.tgz", + "integrity": "sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==" }, "cookie-signature": { "version": "1.0.6", @@ -37468,49 +32829,45 @@ "dev": true }, "crc32-stream": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-4.0.3.tgz", - "integrity": "sha512-NT7w2JVU7DFroFdYkeq8cywxrgjPHWkdX1wjpRQXPX5Asews3tA+Ght6lddQO5Mkumffp3X7GEqku3epj2toIw==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", + "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", "dev": true, "requires": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "dev": true, + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } - } - } - }, - "criteo-direct-rsa-validate": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/criteo-direct-rsa-validate/-/criteo-direct-rsa-validate-1.1.0.tgz", - "integrity": "sha512-7gQ3zX+d+hS/vOxzLrZ4aRAceB7qNJ0VzaGNpcWjDCmtOpASB50USJDupTik/H2nHgiSAA3VNZ3SFuONs8LR9Q==" - }, - "cross-fetch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz", - "integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==", - "dev": true, - "requires": { - "node-fetch": "2.6.7" - }, - "dependencies": { - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "whatwg-url": "^5.0.0" + "safe-buffer": "~5.2.0" } } } @@ -37694,11 +33051,18 @@ "dev": true }, "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", + "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", "requires": { - "ms": "2.1.2" + "ms": "^2.1.3" + }, + "dependencies": { + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } } }, "debug-fabulous": { @@ -37744,23 +33108,6 @@ "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", "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": "4.1.3", "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", @@ -37802,12 +33149,6 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, - "deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "dev": true - }, "deepmerge-ts": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", @@ -37821,6 +33162,14 @@ "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-resolution": { @@ -37834,6 +33183,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "optional": true, "requires": { "clone": "^1.0.2" }, @@ -37842,16 +33192,11 @@ "version": "1.0.4", "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", - "dev": true + "dev": true, + "optional": true } } }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true - }, "define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -37955,436 +33300,12 @@ "integrity": "sha512-DtCOLG98P007x7wiiOmfI0fi3eIKyWiLTGJ2MDnVi/E04lWGbf+JzrRHMm0rgIIZJGtHpKpbVgLWHrv8xXpc3Q==", "dev": true }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha512-BDKtmHlOzwI7iRuEkhzsnPoi5ypEhWAJB5RvHWe1kMr06js3uK5B3734i3ui5Yd+wOJV1cpE4JnivPD283GU/A==", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, "detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", "integrity": "sha512-CwffZFvlJffUg9zZA0uqrjQayUTC8ob94pnr5sFwaVv3IOmkfUHcWH+jXaQK3askE51Cqe8/9Ql/0uXNwqZ8Zg==", "dev": true }, - "devtools": { - "version": "7.35.0", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-7.35.0.tgz", - "integrity": "sha512-7HMZMcJSCK/PaBCWVs4n4ZhtBNdUQj10iPwXvj/JDkqPreEXN/XW9GJAoMuLPFmCEKfxe+LrIbgs8ocGJ6rp/A==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "@types/ua-parser-js": "^0.7.33", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "chrome-launcher": "^0.15.0", - "edge-paths": "^2.1.0", - "puppeteer-core": "13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.1", - "uuid": "^9.0.0" - }, - "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "@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 - }, - "@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", - "dev": true, - "requires": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" - } - }, - "@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", - "dev": true, - "requires": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true - }, - "@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", - "dev": true, - "requires": { - "@types/node": "^18.0.0", - "got": "^11.8.1" - } - }, - "@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", - "dev": true, - "requires": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" - } - }, - "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" - } - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "dev": true - }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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 - }, - "debug": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", - "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - }, - "devtools-protocol": { - "version": "0.0.948846", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.948846.tgz", - "integrity": "sha512-5fGyt9xmMqUl2VI7+rnUkKCiAQIpLns8sfQtTENy5L70ktbNw0Z3TFJ1JoFNYdx/jffz4YXU45VF75wKZD7sZQ==", - "dev": true - }, - "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": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "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" - } - }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - } - }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", - "dev": true, - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" - } - }, - "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 - }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - } - }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "dev": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "dev": true - }, - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "dev": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "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 - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "dev": true - }, - "puppeteer-core": { - "version": "13.1.3", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.1.3.tgz", - "integrity": "sha512-96pzvVBzq5lUGt3L/QrIH3mxn3NfZylHeusNhq06xBAHPI0Upc0SC/9u7tXjL0oRnmcExeVRJivr1lj7Ah/yDQ==", - "dev": true, - "requires": { - "debug": "4.3.2", - "devtools-protocol": "0.0.948846", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.0", - "node-fetch": "2.6.7", - "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": "8.2.3" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", - "dev": true, - "requires": { - "lowercase-keys": "^2.0.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - }, - "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.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - }, - "ws": { - "version": "8.2.3", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz", - "integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==", - "dev": true, - "requires": {} - } - } - }, - "devtools-protocol": { - "version": "0.0.1260888", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1260888.tgz", - "integrity": "sha512-9rTIZ4ZjWwalCPiaY+kPiALLfOKgAz5CTi/Zb1L+qSZ8PH3zVo1T8JcgXIIqg1iM3pZ6hF+n9xO5r2jZ/SF+jg==", - "dev": true - }, "di": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", @@ -38742,9 +33663,9 @@ "dev": true }, "dset": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.2.tgz", - "integrity": "sha512-g/M9sqy3oHe477Ar4voQxWtaPIFw1jTdKZuomOjhCcBx9nHUNn0pu6NopuFFrTh/TRZIKEj+76vLWFu9BNKk+Q==" + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/dset/-/dset-3.1.4.tgz", + "integrity": "sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==" }, "duplexer": { "version": "0.1.2", @@ -38895,16 +33816,17 @@ } }, "edgedriver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.5.0.tgz", - "integrity": "sha512-qQIjDQ62cbDcoZ8AcN6PxQekOwGDZcTkdZj5qr6Ew1i4mMi3R0d1Y6DKlyUnkBs5GXUYua5wKB0XHMLj6FAChQ==", + "version": "5.6.1", + "resolved": "https://registry.npmjs.org/edgedriver/-/edgedriver-5.6.1.tgz", + "integrity": "sha512-3Ve9cd5ziLByUdigw6zovVeWJjVs8QHVmqOB0sJ0WNeVPcwf4p18GnxMmVvlFmYRloUwf5suNuorea4QzwBIOA==", "dev": true, "requires": { - "@wdio/logger": "^8.28.0", + "@wdio/logger": "^8.38.0", + "@zip.js/zip.js": "^2.7.48", "decamelize": "^6.0.0", "edge-paths": "^3.0.5", + "fast-xml-parser": "^4.4.1", "node-fetch": "^3.3.2", - "unzipper": "^0.11.6", "which": "^4.0.0" } }, @@ -38923,9 +33845,9 @@ } }, "electron-to-chromium": { - "version": "1.4.774", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.774.tgz", - "integrity": "sha512-132O1XCd7zcTkzS3FgkAzKmnBuNJjK8WjcTtNuoylj7MYbqw5eXehjQ5OK91g0zm7OTKIPeaAG4CPoRfD9M1Mg==" + "version": "1.5.70", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.70.tgz", + "integrity": "sha512-P6FPqAWIZrC3sHDAwBitJBs7N7IF58m39XVny7DFseQXK2eiMn7nNQizFf63mWDDUnFvaqsM8FI0+ZZfLkdUGA==" }, "emoji-regex": { "version": "8.0.0", @@ -38942,7 +33864,29 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "dev": true + }, + "encoding-sniffer": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.0.tgz", + "integrity": "sha512-ju7Wq1kg04I3HtiYIOrUrdfdDvkyO9s5XM8QAj/bN61Yo/Vb4vgJxy5vi4Yxk01gWHbrofpPtpxM8bKger9jhg==", + "dev": true, + "requires": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } + } }, "end-of-stream": { "version": "1.4.4", @@ -38964,7 +33908,7 @@ "@types/node": ">=10.0.0", "accepts": "~1.3.4", "base64id": "2.0.0", - "cookie": "~0.4.1", + "cookie": "~0.7.2", "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", @@ -38972,9 +33916,9 @@ }, "dependencies": { "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "dev": true } } @@ -39250,10 +34194,42 @@ "es6-symbol": "^3.1.1" } }, + "esbuild": { + "version": "0.23.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz", + "integrity": "sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==", + "dev": true, + "requires": { + "@esbuild/aix-ppc64": "0.23.1", + "@esbuild/android-arm": "0.23.1", + "@esbuild/android-arm64": "0.23.1", + "@esbuild/android-x64": "0.23.1", + "@esbuild/darwin-arm64": "0.23.1", + "@esbuild/darwin-x64": "0.23.1", + "@esbuild/freebsd-arm64": "0.23.1", + "@esbuild/freebsd-x64": "0.23.1", + "@esbuild/linux-arm": "0.23.1", + "@esbuild/linux-arm64": "0.23.1", + "@esbuild/linux-ia32": "0.23.1", + "@esbuild/linux-loong64": "0.23.1", + "@esbuild/linux-mips64el": "0.23.1", + "@esbuild/linux-ppc64": "0.23.1", + "@esbuild/linux-riscv64": "0.23.1", + "@esbuild/linux-s390x": "0.23.1", + "@esbuild/linux-x64": "0.23.1", + "@esbuild/netbsd-x64": "0.23.1", + "@esbuild/openbsd-arm64": "0.23.1", + "@esbuild/openbsd-x64": "0.23.1", + "@esbuild/sunos-x64": "0.23.1", + "@esbuild/win32-arm64": "0.23.1", + "@esbuild/win32-ia32": "0.23.1", + "@esbuild/win32-x64": "0.23.1" + } + }, "escalade": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", - "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==" + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==" }, "escape-html": { "version": "1.0.3", @@ -39263,7 +34239,8 @@ "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true }, "escodegen": { "version": "1.8.1", @@ -39515,9 +34492,9 @@ } }, "eslint-module-utils": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.8.1.tgz", - "integrity": "sha512-rXDXR3h7cs7dy9RNpUlQf80nX31XWJEyGq1tRMo+6GsO5VmTe4UTwtmonAD4ZkAsrfMVDA2wlGJ3790Ys+D49Q==", + "version": "2.12.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", "dev": true, "requires": { "debug": "^3.2.7" @@ -39545,27 +34522,29 @@ } }, "eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", "dev": true, "requires": { - "array-includes": "^3.1.7", - "array.prototype.findlastindex": "^1.2.3", + "@rtsao/scc": "^1.1.0", + "array-includes": "^3.1.8", + "array.prototype.findlastindex": "^1.2.5", "array.prototype.flat": "^1.3.2", "array.prototype.flatmap": "^1.3.2", "debug": "^3.2.7", "doctrine": "^2.1.0", "eslint-import-resolver-node": "^0.3.9", - "eslint-module-utils": "^2.8.0", - "hasown": "^2.0.0", - "is-core-module": "^2.13.1", + "eslint-module-utils": "^2.12.0", + "hasown": "^2.0.2", + "is-core-module": "^2.15.1", "is-glob": "^4.0.3", "minimatch": "^3.1.2", - "object.fromentries": "^2.0.7", - "object.groupby": "^1.0.1", - "object.values": "^1.1.7", + "object.fromentries": "^2.0.8", + "object.groupby": "^1.0.3", + "object.values": "^1.2.0", "semver": "^6.3.1", + "string.prototype.trimend": "^1.0.8", "tsconfig-paths": "^3.15.0" }, "dependencies": { @@ -39590,19 +34569,21 @@ } }, "eslint-plugin-jsdoc": { - "version": "38.1.6", - "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-38.1.6.tgz", - "integrity": "sha512-n4s95oYlg0L43Bs8C0dkzIldxYf8pLCutC/tCbjIdF7VDiobuzPI+HZn9Q0BvgOvgPNgh5n7CSStql25HUG4Tw==", + "version": "48.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-jsdoc/-/eslint-plugin-jsdoc-48.5.0.tgz", + "integrity": "sha512-ukXPNpGby3KjCveCizIS8t1EbuJEHYEu/tBg8GCbn/YbHcXwphyvYCdvRZ/oMRfTscGSSzfsWoZ+ZkAP0/6YMQ==", "dev": true, "requires": { - "@es-joy/jsdoccomment": "~0.22.1", - "comment-parser": "1.3.1", + "@es-joy/jsdoccomment": "~0.43.1", + "are-docs-informative": "^0.0.2", + "comment-parser": "1.4.1", "debug": "^4.3.4", "escape-string-regexp": "^4.0.0", - "esquery": "^1.4.0", - "regextras": "^0.8.0", - "semver": "^7.3.5", - "spdx-expression-parse": "^3.0.1" + "esquery": "^1.5.0", + "parse-imports": "^2.1.0", + "semver": "^7.6.2", + "spdx-expression-parse": "^4.0.0", + "synckit": "^0.9.0" }, "dependencies": { "escape-string-regexp": { @@ -39616,6 +34597,16 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz", "integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==", "dev": true + }, + "spdx-expression-parse": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-4.0.0.tgz", + "integrity": "sha512-Clya5JIij/7C6bRR22+tnGXbc4VKlibKSVj2iHvVeX5iMW7s1SIQlqu699JkODJJIhh/pUu8L0/VLh8xflD+LQ==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } } } }, @@ -39996,617 +34987,37 @@ "jest-util": "^29.7.0" } }, - "expect-webdriverio": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-4.13.0.tgz", - "integrity": "sha512-y2LF6vECifTOiOk0i0FMPWJGyntiW+eqzQWFZimu9Cae8XMUH5q4F5SzpfGCxsvmOdf3uFjlr+u9IXrQBN5o0Q==", - "dev": true, - "requires": { - "@vitest/snapshot": "^1.2.2", - "@wdio/globals": "^8.29.3", - "@wdio/logger": "^8.28.0", - "expect": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "lodash.isequal": "^4.5.0", - "webdriverio": "^8.29.3" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", - "dev": true, - "optional": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "progress": "2.0.3", - "proxy-agent": "6.3.0", - "tar-fs": "3.0.4", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", - "dev": true, - "optional": true, - "requires": { - "debug": "^4.3.4" - } - }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.2", - "async": "^3.2.4", - "buffer-crc32": "^1.0.0", - "readable-stream": "^4.0.0", - "readdir-glob": "^1.1.2", - "tar-stream": "^3.0.0", - "zip-stream": "^6.0.1" - } - }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", - "dev": true, - "optional": true, - "requires": { - "glob": "^10.0.0", - "graceful-fs": "^4.2.0", - "is-stream": "^2.0.1", - "lazystream": "^1.0.0", - "lodash": "^4.17.15", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "async": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz", - "integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==", - "dev": true, - "optional": true - }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0" - } - }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "dev": true, - "optional": true, - "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "buffer-crc32": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-1.0.0.tgz", - "integrity": "sha512-Db1SbgBS/fg/392AblrMJk97KggmvYhr4pB5ZIMTWtaivCPMWLkmb7m21cJvpvgK+J3nsU2CmmixNBZx4vFj/w==", - "dev": true, - "optional": true - }, - "chrome-launcher": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/chrome-launcher/-/chrome-launcher-1.1.1.tgz", - "integrity": "sha512-OAQgBmpUzrIuShApIwOpjt7WFripGKcDMW/qeYU+kcl6jBPg87mRG+N2C3Vu+VeCVPqZ/ds3GfI2TK7tpz3Yyw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "*", - "escape-string-regexp": "^4.0.0", - "is-wsl": "^2.2.0", - "lighthouse-logger": "^2.0.1" - } - }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", - "dev": true, - "optional": true, - "requires": { - "crc-32": "^1.2.0", - "crc32-stream": "^6.0.0", - "is-stream": "^2.0.1", - "normalize-path": "^3.0.0", - "readable-stream": "^4.0.0" - } - }, - "crc32-stream": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz", - "integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==", - "dev": true, - "optional": true, - "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - } - }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", - "dev": true, - "optional": true, - "requires": { - "node-fetch": "^2.6.12" - } - }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "chrome-launcher": "^1.0.0", - "edge-paths": "^3.0.5", - "import-meta-resolve": "^4.0.0", - "puppeteer-core": "20.3.0", - "query-selector-shadow-dom": "^1.0.0", - "ua-parser-js": "^1.0.37", - "uuid": "^9.0.0", - "which": "^4.0.0" - }, - "dependencies": { - "@puppeteer/browsers": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.3.0.tgz", - "integrity": "sha512-an3QdbNPkuU6qpxpbssxAbjRLJcF+eP4L8UqIY3+6n0sbaVxw5pz7PiCLy9g32XEZuoamUlV5ZQPnA6FxvkIHA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "4.3.4", - "extract-zip": "2.0.1", - "http-proxy-agent": "5.0.0", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "yargs": "17.7.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "debug": "4" - } - }, - "chromium-bidi": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.9.tgz", - "integrity": "sha512-u3DC6XwgLCA9QJ5ak1voPslCmacQdulZNCPsI3qNXxSnEcZS7DFIbww+5RM2bznMEje7cc0oydavRLRvOIZtHw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "mitt": "3.0.0" - } - }, - "cross-fetch": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.6.tgz", - "integrity": "sha512-riRvo06crlE8HiqOwIpQhxwdOk4fOeR7FVM/wXoxchFEqMNUjvbs3bfo4OTgMEMHzppd4DxFBDbyySj8Cv781g==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "node-fetch": "^2.6.11" - } - }, - "devtools-protocol": { - "version": "0.0.1120988", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1120988.tgz", - "integrity": "sha512-39fCpE3Z78IaIPChJsP6Lhmkbf4dWXOmzLk/KFTdRkNk/0JymRIfUynDVRndV9HoDz8PyalK1UH21ST/ivwW5Q==", - "dev": true, - "optional": true, - "peer": true - }, - "http-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", - "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - } - }, - "https-proxy-agent": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", - "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "agent-base": "6", - "debug": "4" - } - }, - "puppeteer-core": { - "version": "20.3.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.3.0.tgz", - "integrity": "sha512-264pBrIui5bO6NJeOcbJrLa0OCwmA4+WK00JMrLIKTfRiqe2gx8KWTzLsjyw/bizErp3TKS7vt/I0i5fTC+mAw==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "@puppeteer/browsers": "1.3.0", - "chromium-bidi": "0.4.9", - "cross-fetch": "3.1.6", - "debug": "4.3.4", - "devtools-protocol": "0.0.1120988", - "ws": "8.13.0" - } - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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, - "optional": true, - "peer": true, - "requires": { - "bl": "^4.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - } - } - }, - "devtools-protocol": { - "version": "0.0.1282316", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1282316.tgz", - "integrity": "sha512-i7eIqWdVxeXBY/M+v83yRkOV1sTHnr3XYiC0YNBivLIE6hBfE2H0c2o8VC5ynT44yjy+Ei0kLrBQFK/RUKaAHQ==", - "dev": true, - "optional": true - }, - "escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "optional": true, - "peer": true - }, - "https-proxy-agent": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.4.tgz", - "integrity": "sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "4" - } - }, - "is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true, - "optional": true - }, - "lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "optional": true, - "peer": true, - "requires": { - "ms": "2.0.0" - } - } - } - }, - "lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "dev": true, - "optional": true - }, - "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^2.0.1" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true, - "optional": true, - "peer": true - }, - "node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "dev": true, - "optional": true, - "requires": { - "whatwg-url": "^5.0.0" - } - }, - "proxy-agent": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.3.0.tgz", - "integrity": "sha512-0LdR757eTj/JfuU7TL2YCuAZnxWXu3tkJbg4Oq3geW/qFNT/32T0sp2HnZ9O0lMR4q3vwAt0+xCA8SR0WAD0og==", - "dev": true, - "optional": true, - "requires": { - "agent-base": "^7.0.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.0", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.0.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.1" - } - }, - "puppeteer-core": { - "version": "20.9.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz", - "integrity": "sha512-H9fYZQzMTRrkboEfPmf7m3CLDN6JvbxXA3qTtS+dFt27tR+CsFHzPsT6pzp6lYL6bJbAPaR0HaPO6uSi+F94Pg==", - "dev": true, - "optional": true, - "requires": { - "@puppeteer/browsers": "1.4.6", - "chromium-bidi": "0.4.16", - "cross-fetch": "4.0.0", - "debug": "4.3.4", - "devtools-protocol": "0.0.1147663", - "ws": "8.13.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.1147663", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1147663.tgz", - "integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==", - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "4.5.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", - "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", - "dev": true, - "optional": true, - "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" - } - }, - "serialize-error": { - "version": "11.0.3", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", - "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", - "dev": true, - "optional": true, - "requires": { - "type-fest": "^2.12.2" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", - "dev": true, - "optional": true - }, - "ua-parser-js": { - "version": "1.0.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-1.0.37.tgz", - "integrity": "sha512-bhTyI94tZofjo+Dn8SN6Zv8nBDvyXTymAdM3LDI/0IboIUwTu1rEhW7v2TfiVsoYWgkQ4kOVqnI8APUFbIQIFQ==", - "dev": true, - "optional": true, - "peer": true - }, - "webdriverio": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-8.36.1.tgz", - "integrity": "sha512-vzE09oFQeMbOYJ/75jZ13sDIljzC3HH7uoUJKAMAEtyrn/bu1F9Sg/4IDEsvQaRD3pz3ae6SkRld33lcQk6HJA==", - "dev": true, - "optional": true, - "requires": { - "@types/node": "^20.1.0", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/repl": "8.24.12", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "archiver": "^7.0.0", - "aria-query": "^5.0.0", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", - "import-meta-resolve": "^4.0.0", - "is-plain-obj": "^4.1.0", - "lodash.clonedeep": "^4.5.0", - "lodash.zip": "^4.2.0", - "minimatch": "^9.0.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" - } - }, - "ws": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz", - "integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==", - "dev": true, - "optional": true, - "requires": {} - }, - "yargs": { - "version": "17.7.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz", - "integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==", - "dev": true, - "optional": true, - "requires": { - "cliui": "^8.0.1", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.3", - "y18n": "^5.0.5", - "yargs-parser": "^21.1.1" - } - }, - "zip-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", - "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", - "dev": true, - "optional": true, - "requires": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - } - } - } - }, "express": { - "version": "4.19.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", - "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "version": "4.21.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.1.tgz", + "integrity": "sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==", "requires": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.2", + "body-parser": "1.20.3", "content-disposition": "0.5.4", "content-type": "~1.0.4", - "cookie": "0.6.0", + "cookie": "0.7.1", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", + "finalhandler": "1.3.1", "fresh": "0.5.2", "http-errors": "2.0.0", - "merge-descriptors": "1.0.1", + "merge-descriptors": "1.0.3", "methods": "~1.1.2", "on-finished": "2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", + "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.18.0", - "serve-static": "1.15.0", + "send": "0.19.0", + "serve-static": "1.16.2", "setprototypeof": "1.2.0", "statuses": "2.0.1", "type-is": "~1.6.18", @@ -40622,10 +35033,52 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + } + } } } }, @@ -40806,6 +35259,15 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "fast-xml-parser": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz", + "integrity": "sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw==", + "dev": true, + "requires": { + "strnum": "^1.0.5" + } + }, "faye-websocket": { "version": "0.10.0", "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.10.0.tgz", @@ -40849,21 +35311,12 @@ } }, "figures": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-5.0.0.tgz", - "integrity": "sha512-ej8ksPF4x6e5wvK9yevct0UCXh8TTFlWGVLlgjZuoBH1HwjIfKE/IdL5mq89sFA7zELi1VhKpmtDnrs7zWyeyg==", + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-6.1.0.tgz", + "integrity": "sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==", "dev": true, "requires": { - "escape-string-regexp": "^5.0.0", - "is-unicode-supported": "^1.2.0" - }, - "dependencies": { - "escape-string-regexp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", - "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", - "dev": true - } + "is-unicode-supported": "^2.0.0" } }, "file-entry-cache": { @@ -40912,21 +35365,21 @@ } }, "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==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "dev": true, "requires": { "to-regex-range": "^5.0.1" } }, "finalhandler": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", - "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.1.tgz", + "integrity": "sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==", "requires": { "debug": "2.6.9", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "on-finished": "2.4.1", "parseurl": "~1.3.3", @@ -40942,6 +35395,11 @@ "ms": "2.0.0" } }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -41088,12 +35546,6 @@ } } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -41227,9 +35679,9 @@ "dev": true }, "foreground-child": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", - "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.0.tgz", + "integrity": "sha512-Ld2g8rrAyMYFXBhEqMz8ZAHBi4J4uS1i/CxGMDnjyFWddMXLVcDp051DZfu+t7+ab7Wv6SMqpWmyFIj5UbfFvg==", "dev": true, "requires": { "cross-spawn": "^7.0.0", @@ -41267,12 +35719,6 @@ "mime-types": "^2.1.12" } }, - "form-data-encoder": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", - "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", - "dev": true - }, "formdata-node": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-5.0.1.tgz", @@ -41317,12 +35763,6 @@ "integrity": "sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==", "dev": true }, - "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": "0.6.4", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.6.4.tgz", @@ -41484,15 +35924,6 @@ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", "dev": true }, - "gaze": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", - "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==", - "dev": true, - "requires": { - "globule": "^1.0.0" - } - }, "geckodriver": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.0.tgz", @@ -41603,6 +36034,15 @@ "get-intrinsic": "^1.2.4" } }, + "get-tsconfig": { + "version": "4.7.6", + "resolved": "https://registry.npmjs.org/get-tsconfig/-/get-tsconfig-4.7.6.tgz", + "integrity": "sha512-ZAqrLlu18NbDdRaHq+AKXzAmqIUPswPWKUchfytdAjiRFnCe5ojG2bstg6mRiZabkKfCoL/e98pbBELIV/YCeA==", + "dev": true, + "requires": { + "resolve-pkg-maps": "^1.0.0" + } + }, "get-uri": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.3.tgz", @@ -41708,16 +36148,17 @@ "dev": true }, "glob": { - "version": "10.3.15", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.15.tgz", - "integrity": "sha512-0c6RlJt1TICLyvJYIApxb8GsXoai0KUP7AxKKAtsYXdgJR1mGEUa7DgwShbdk1nly0PYoZj01xd4hzbq3fsjpw==", + "version": "10.4.5", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", + "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "dev": true, "requires": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, "dependencies": { "brace-expansion": { @@ -41975,6 +36416,17 @@ "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": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "is-plain-object": { @@ -41986,15 +36438,6 @@ "isobject": "^3.0.1" } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -42034,12 +36477,6 @@ "requires": { "is-plain-object": "^2.0.4" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true } } }, @@ -42144,42 +36581,6 @@ "gopd": "^1.0.1" } }, - "globule": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz", - "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==", - "dev": true, - "requires": { - "glob": "~7.1.1", - "lodash": "^4.17.21", - "minimatch": "~3.0.2" - }, - "dependencies": { - "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": { - "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" - } - }, - "minimatch": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz", - "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, "glogg": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.2.tgz", @@ -42197,33 +36598,6 @@ "get-intrinsic": "^1.1.3" } }, - "got": { - "version": "12.6.1", - "resolved": "https://registry.npmjs.org/got/-/got-12.6.1.tgz", - "integrity": "sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==", - "dev": true, - "requires": { - "@sindresorhus/is": "^5.2.0", - "@szmarczak/http-timer": "^5.0.1", - "cacheable-lookup": "^7.0.0", - "cacheable-request": "^10.2.8", - "decompress-response": "^6.0.0", - "form-data-encoder": "^2.1.2", - "get-stream": "^6.0.1", - "http2-wrapper": "^2.1.10", - "lowercase-keys": "^3.0.0", - "p-cancelable": "^3.0.0", - "responselike": "^3.0.0" - }, - "dependencies": { - "get-stream": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", - "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", - "dev": true - } - } - }, "graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -42552,99 +36926,6 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-2.0.5.tgz", "integrity": "sha512-yAdfUZ+c2wetVNIFsNRn44THW+Lty6S5TwMpUfLA/UaGhiXbBv/F8E60/1hMLd0cnF/CDoWH8vzVaI5bAcHCjw==", "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", - "dev": true - }, - "http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "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 - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "requires": { - "ee-first": "1.1.1" - } - }, - "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.2", - "mime": "1.4.1", - "ms": "2.0.0", - "on-finished": "~2.3.0", - "range-parser": "~1.2.0", - "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 } } }, @@ -43741,7 +38022,8 @@ "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true }, "has-gulplog": { "version": "0.1.0", @@ -43967,16 +38249,6 @@ "integrity": "sha512-fJ7cW7fQGCYAkgv4CPfwFHrfd/cLS4Hau96JuJ+ZTOWhjnhoeN1ub1tFmALm/+lW5z4WCAuAV9bm05AP0mS6Gw==", "dev": true }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha512-ycURW7oUxE2sNiPVw1HVEFsW+ecOpJ5zaj7eC0RlwhibhRBod20muUN8qu/gzx956YrLolVvs1MTXwKgC2rVEg==", - "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", @@ -44015,12 +38287,24 @@ "integrity": "sha512-0quDb7s97CfemeJAnW9wC0hw78MtW7NU3hqtCD75g2vFlDLt36llsYD7uB7SUzojLMP24N5IatXf7ylGXiGG9A==", "dev": true }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==", + "htmlfy": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.3.2.tgz", + "integrity": "sha512-FsxzfpeDYRqn1emox9VpxMPfGjADoUmmup8D604q497R0VNxiXs4ZZTN2QzkaMA5C9aHGUoe1iQRVSm+HK9xuA==", "dev": true }, + "htmlparser2": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-9.1.0.tgz", + "integrity": "sha512-5zfg6mHUoaer/97TxnGpxmbR7zJtPwIYFMZ/H5ucTlPZhKvtum05yiPK3Mgai3a0DyVxv7qYqoweaEd2nrYQzQ==", + "dev": true, + "requires": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.1.0", + "entities": "^4.5.0" + } + }, "http-errors": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", @@ -44082,16 +38366,6 @@ "sshpk": "^1.7.0" } }, - "http2-wrapper": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", - "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", - "dev": true, - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.2.0" - } - }, "https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -44103,9 +38377,9 @@ } }, "human-signals": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", - "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.0.tgz", + "integrity": "sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==", "dev": true }, "iconv-lite": { @@ -44128,6 +38402,12 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "immediate": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", + "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==", + "dev": true + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -44186,43 +38466,19 @@ "dev": true }, "inquirer": { - "version": "9.2.12", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.12.tgz", - "integrity": "sha512-mg3Fh9g2zfuVWJn6lhST0O7x4n03k7G8Tx5nvikJkbq8/CK47WDVm+UznF0G6s5Zi0KcyUisr6DU8T67N5U+1Q==", + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-11.1.0.tgz", + "integrity": "sha512-CmLAZT65GG/v30c+D2Fk8+ceP6pxD6RL+hIUOWAltCmeyEqWYwqu9v76q03OvjyZ3AB0C1Ala2stn1z/rMqGEw==", "dev": true, "requires": { - "@ljharb/through": "^2.3.11", + "@inquirer/core": "^9.2.1", + "@inquirer/prompts": "^6.0.1", + "@inquirer/type": "^2.0.0", + "@types/mute-stream": "^0.0.4", "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^5.0.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", + "mute-stream": "^1.0.0", "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" - }, - "dependencies": { - "chalk": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", - "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + "rxjs": "^7.8.1" } }, "internal-slot": { @@ -44242,15 +38498,6 @@ "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true }, - "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", @@ -44371,11 +38618,11 @@ "dev": true }, "is-core-module": { - "version": "2.13.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", - "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.15.1.tgz", + "integrity": "sha512-z0vtXSwucUJtANQWldhbtbt7BnL0vxiFjIdDLAatwhDYty2bad6s+rijD6Ri4YuYJubLzIJLUidCh09e1djEVQ==", "requires": { - "hasown": "^2.0.0" + "hasown": "^2.0.2" } }, "is-data-descriptor": { @@ -44445,12 +38692,6 @@ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "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", @@ -44481,12 +38722,6 @@ "is-extglob": "^2.1.1" } }, - "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.3", "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", @@ -44646,9 +38881,9 @@ } }, "is-unicode-supported": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", - "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.1.0.tgz", + "integrity": "sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==", "dev": true }, "is-utf8": { @@ -44907,9 +39142,9 @@ } }, "jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz", + "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==", "dev": true, "requires": { "@isaacs/cliui": "^8.0.2", @@ -45174,12 +39409,6 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true - }, "supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -45312,15 +39541,15 @@ "dev": true }, "jsdoc-type-pratt-parser": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-2.2.5.tgz", - "integrity": "sha512-2a6eRxSxp1BW040hFvaJxhsCMI9lT8QB8t14t+NY5tC5rckIR0U9cr2tjOeaFirmEOy6MHvmJnY7zTBHq431Lw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsdoc-type-pratt-parser/-/jsdoc-type-pratt-parser-4.0.0.tgz", + "integrity": "sha512-YtOli5Cmzy3q4dP26GraSOeAhqecewG04hoO8DY56CH4KJ9Fvv5qKWUCCo3HZob7esJQHCv6/+bnTy72xZZaVQ==", "dev": true }, "jsesc": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", - "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==" + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.0.2.tgz", + "integrity": "sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==" }, "json-buffer": { "version": "3.0.1", @@ -45381,6 +39610,18 @@ "verror": "1.10.0" } }, + "jszip": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.10.1.tgz", + "integrity": "sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==", + "dev": true, + "requires": { + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "setimmediate": "^1.0.5" + } + }, "just-debounce": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", @@ -45877,9 +40118,9 @@ } }, "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==", + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true }, "kleur": { @@ -45903,12 +40144,6 @@ "@babel/traverse": "^7.10.5" } }, - "ky": { - "version": "0.33.3", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.33.3.tgz", - "integrity": "sha512-CasD9OCEQSFIam2U8efFK81Yeg8vNMTBUqtMOHlrcWQHqUX3HeCl9Dr31u4toV7emlH8Mymk5+9p0lL6mKb/Xw==", - "dev": true - }, "last-run": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/last-run/-/last-run-1.1.1.tgz", @@ -45962,6 +40197,15 @@ "type-check": "~0.4.0" } }, + "lie": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", + "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", + "dev": true, + "requires": { + "immediate": "~3.0.5" + } + }, "liftoff": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-3.1.0.tgz", @@ -45989,33 +40233,6 @@ } } }, - "lighthouse-logger": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-1.4.2.tgz", - "integrity": "sha512-gPWxznF6TKmUHrOQjlVo2UbaL2EJ71mb2CCeRs/2qBpi4L/g4LUVc9+3lKQ6DTUZwJswfM7ainGrLO1+fOqa2g==", - "dev": true, - "requires": { - "debug": "^2.6.9", - "marky": "^1.2.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - } - } - }, "lines-and-columns": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-2.0.4.tgz", @@ -46029,16 +40246,16 @@ "dev": true }, "live-connect-common": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-3.1.4.tgz", - "integrity": "sha512-NK5HH0b/6bQX6hZQttlDfqrpDiP+iYtYYGO47LfM9YVwT1OZITgYZUJ0oG4IVynwdpas/VGvXv5hN0UcVK97oQ==" + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/live-connect-common/-/live-connect-common-4.1.0.tgz", + "integrity": "sha512-sRklgbe13377aR+G0qCBiZPayQw5oZZozkuxKEoyipxscLbVzwe9gtA7CPpbmo6UcOdQxdCE6A7J1tI0wTSmqw==" }, "live-connect-js": { - "version": "6.7.3", - "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-6.7.3.tgz", - "integrity": "sha512-K2/GGhyhJ7/bFJfjiNw41W5xLRER9Smc49a8A6PImCcgit/sp2UsYz/F+sQwoj8IkJ3PufHvBnIGBbeQ31VsBg==", + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/live-connect-js/-/live-connect-js-7.1.0.tgz", + "integrity": "sha512-fFxvQjOsHkCjulWsbirjxb6Y8xuAoWdgYqZvBLoSVKry48IyvVnLfvWgJg66qENjxig+8RH9bvlE16I6hJ7J7Q==", "requires": { - "live-connect-common": "^v3.1.4", + "live-connect-common": "^v4.1.0", "tiny-hashes": "1.0.1" } }, @@ -46208,18 +40425,6 @@ "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", "integrity": "sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==" }, - "lodash.defaults": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==", - "dev": true - }, - "lodash.difference": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==", - "dev": true - }, "lodash.escape": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", @@ -46229,12 +40434,6 @@ "lodash._root": "^3.0.0" } }, - "lodash.flatten": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==", - "dev": true - }, "lodash.flattendeep": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", @@ -46265,18 +40464,6 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "dev": true }, - "lodash.isobject": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/lodash.isobject/-/lodash.isobject-3.0.2.tgz", - "integrity": "sha512-3/Qptq2vr7WeJbB4KHUSKlq8Pl7ASXi3UG6CMbBm8WRtXi8+GHm7mKaU3urfpSEzWe2wCIChs6/sdocUsTKJiA==", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", - "dev": true - }, "lodash.keys": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", @@ -46423,15 +40610,6 @@ "integrity": "sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==", "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" - } - }, "loupe": { "version": "2.3.7", "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", @@ -46441,12 +40619,6 @@ "get-func-name": "^2.0.1" } }, - "lowercase-keys": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", - "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", - "dev": true - }, "lru-cache": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", @@ -46476,12 +40648,12 @@ } }, "magic-string": { - "version": "0.30.10", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.10.tgz", - "integrity": "sha512-iIRwTIf0QKV3UAnYK4PU8uiEc4SRh5jX0mwpIwETPpHdhVM4f53RSwS/vXvN1JhGX+Cs7B8qIq3d6AH49O5fAQ==", + "version": "0.30.14", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.14.tgz", + "integrity": "sha512-5c99P1WKTed11ZC0HMJOj6CDIue6F8ySu+bJL+85q1zBEIY8IklrJ1eiKC2NDRh3Ct3FcvmJPyQHb9erXMTJNw==", "dev": true, "requires": { - "@jridgewell/sourcemap-codec": "^1.4.15" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "make-dir": { @@ -46500,14 +40672,6 @@ "dev": true, "requires": { "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - } } }, "map-cache": { @@ -46537,12 +40701,6 @@ "integrity": "sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==", "dev": true }, - "marky": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/marky/-/marky-1.2.5.tgz", - "integrity": "sha512-q9JtQJKjpsVxCRVgQ+WapguSbKC3SQ5HEzFGPAJMStgh3QjCawp00UKv3MTTAArTmGmmPUvllHZoNbZ3gs0I+Q==", - "dev": true - }, "matchdep": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/matchdep/-/matchdep-2.0.0.tgz", @@ -46682,12 +40840,6 @@ } } }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true - }, "micromatch": { "version": "3.1.10", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", @@ -46959,13 +41111,13 @@ "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" }, "memoizee": { - "version": "0.4.15", - "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.15.tgz", - "integrity": "sha512-UBWmJpLZd5STPm7PMUlOw/TSy972M+z8gcyQ5veOnSDRREz/0bmpyTfKt3/51DhEBqCZQn1udM/5flcSPYhkdQ==", + "version": "0.4.17", + "resolved": "https://registry.npmjs.org/memoizee/-/memoizee-0.4.17.tgz", + "integrity": "sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==", "dev": true, "requires": { - "d": "^1.0.1", - "es5-ext": "^0.10.53", + "d": "^1.0.2", + "es5-ext": "^0.10.64", "es6-weak-map": "^2.0.3", "event-emitter": "^0.3.5", "is-promise": "^2.2.2", @@ -46985,9 +41137,9 @@ } }, "merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==" }, "merge-stream": { "version": "2.0.0", @@ -47326,12 +41478,12 @@ "dev": true }, "micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, "requires": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" } }, @@ -47360,12 +41512,6 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, - "mimic-response": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", - "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", - "dev": true - }, "min-document": { "version": "2.19.0", "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", @@ -47391,15 +41537,9 @@ "dev": true }, "minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", - "dev": true - }, - "mitt": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.0.tgz", - "integrity": "sha512-7dX2/10ITVyqh4aOSVI9gdape+t9l2/8QxHrFmUXu4EEUpdlxl6RudZUPZoc+zuY2hk1j7XxVroIVIan/pD/SQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "dev": true }, "mixin-deep": { @@ -47428,39 +41568,33 @@ "dev": true }, "mocha": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.4.0.tgz", - "integrity": "sha512-eqhGB8JKapEYcC4ytX/xrzKforgEc3j1pGlAXVy3eRwrtAy5/nIfT1SvgGzfN0XZZxeLq0aQWkOUAmqIJiv+bA==", + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", "dev": true, "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "8.1.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" }, "dependencies": { - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, "ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -47506,22 +41640,6 @@ } } }, - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, "cliui": { "version": "7.0.4", "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", @@ -47548,12 +41666,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, "escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -47624,9 +41736,9 @@ } }, "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -47707,9 +41819,9 @@ } }, "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", + "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 }, "yocto-queue": { @@ -47792,7 +41904,8 @@ "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true }, "multipipe": { "version": "0.1.2", @@ -47873,12 +41986,6 @@ "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "dev": true } } }, @@ -47962,9 +42069,9 @@ } }, "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", "dev": true, "requires": { "isarray": "0.0.1" @@ -48000,9 +42107,9 @@ } }, "node-releases": { - "version": "2.0.14", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", - "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.18.tgz", + "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==" }, "node-request-interceptor": { "version": "0.6.3", @@ -48060,12 +42167,6 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, - "normalize-url": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.0.1.tgz", - "integrity": "sha512-IO9QvjUMWxPQQhs60oOu10CRkWCiZzSUkzbXGGV9pviYl1fXYcvkzQ5jV9z8Y6un8ARoVRl4EtC6v6jNqbaJ/w==", - "dev": true - }, "now-and-later": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/now-and-later/-/now-and-later-2.0.1.tgz", @@ -48352,99 +42453,6 @@ "word-wrap": "^1.2.5" } }, - "ora": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", - "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", - "dev": true, - "requires": { - "bl": "^4.1.0", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-spinners": "^2.5.0", - "is-interactive": "^1.0.0", - "is-unicode-supported": "^0.1.0", - "log-symbols": "^4.1.0", - "strip-ansi": "^6.0.0", - "wcwidth": "^1.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "requires": { - "color-convert": "^2.0.1" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "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 - }, - "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 - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "ordered-read-streams": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-1.0.1.tgz", @@ -48454,12 +42462,6 @@ "readable-stream": "^2.0.1" } }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", - "dev": true - }, "os-locale": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", @@ -48475,24 +42477,12 @@ "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "dev": true }, - "p-cancelable": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", - "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", - "dev": true - }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", "dev": true }, - "p-iteration": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/p-iteration/-/p-iteration-1.1.8.tgz", - "integrity": "sha512-IMFBSDIYcPNnW7uWYGrBqmvTiq7W0uB0fJn6shQZs7dlF3OvrHOre+JT9ikSZ7gZS3vWqclVgoQSvToJrns7uQ==", - "dev": true - }, "p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -48564,6 +42554,18 @@ "netmask": "^2.0.2" } }, + "package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==", + "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", @@ -48584,6 +42586,16 @@ "path-root": "^0.1.1" } }, + "parse-imports": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.2.1.tgz", + "integrity": "sha512-OL/zLggRp8mFhKL0rNORUTR4yBYujK/uU+xZL+/0Rgm2QE4nLO9v8PzEweSJEbMGKmDRjJE4R3IMJlL2di4JeQ==", + "dev": true, + "requires": { + "es-module-lexer": "^1.5.3", + "slashes": "^3.0.12" + } + }, "parse-json": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-7.1.1.tgz", @@ -48647,6 +42659,47 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz", + "integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==", + "dev": true, + "requires": { + "domhandler": "^5.0.3", + "parse5": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "requires": { + "entities": "^4.5.0" + } + } + } + }, + "parse5-parser-stream": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz", + "integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==", + "dev": true, + "requires": { + "parse5": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.2.1.tgz", + "integrity": "sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==", + "dev": true, + "requires": { + "entities": "^4.5.0" + } + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -48721,9 +42774,9 @@ } }, "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "version": "0.1.10", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.10.tgz", + "integrity": "sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==" }, "path-type": { "version": "1.1.0", @@ -48778,9 +42831,9 @@ "dev": true }, "picocolors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.1.tgz", - "integrity": "sha512-anP1Z8qwhkbmu7MFP5iTt+wQKXgwzf7zTyGlcdzabySa9vd0Xt392U0rVmz9poOaBj0uHJKyyo9/upk0HrEQew==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==" }, "picomatch": { "version": "2.3.1", @@ -48809,6 +42862,12 @@ "pinkie": "^2.0.0" } }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, "pkcs7": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/pkcs7/-/pkcs7-1.0.4.tgz", @@ -48910,12 +42969,6 @@ "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==", - "dev": true - }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -49075,77 +43128,6 @@ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", "dev": true }, - "puppeteer-core": { - "version": "13.7.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-13.7.0.tgz", - "integrity": "sha512-rXja4vcnAzFAP1OVLq/5dWNfwBGuzcOARJ6qGV7oAZhnLmVRU8G5MsdeQEAOy332ZhkIOnn9jp15R89LKHyp2Q==", - "dev": true, - "requires": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.981744", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.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": "8.5.0" - }, - "dependencies": { - "devtools-protocol": { - "version": "0.0.981744", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.981744.tgz", - "integrity": "sha512-0cuGS8+jhR67Fy7qG3i3Pc7Aw494sb9yG9QgpG97SFVWwolgYjlhJg7n+UaHxOQT30d1TYu/EYe9k01ivLErIg==", - "dev": true - }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "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.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" - } - }, - "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.0.3", - "end-of-stream": "^1.4.1", - "fs-constants": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1" - } - }, - "ws": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.5.0.tgz", - "integrity": "sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==", - "dev": true, - "requires": {} - } - } - }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -49159,11 +43141,11 @@ "dev": true }, "qs": { - "version": "6.11.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", - "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.0.4" + "side-channel": "^1.0.6" } }, "query-selector-shadow-dom": { @@ -49184,12 +43166,6 @@ "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", - "dev": true - }, "randombytes": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", @@ -49464,12 +43440,6 @@ "unicode-match-property-value-ecmascript": "^2.1.0" } }, - "regextras": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/regextras/-/regextras-0.8.0.tgz", - "integrity": "sha512-k519uI04Z3SaY0fLX843MRXnDeG2+vHOFsyhiPZvNLe7r8rD2YNRjq4BQLZZ0oAr2NrtvZlICsXysGNFPGa3CQ==", - "dev": true - }, "regjsparser": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.9.1.tgz", @@ -49625,15 +43595,6 @@ "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", "dev": true }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } - }, "replace-ext": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", @@ -49738,12 +43699,6 @@ "supports-preserve-symlinks-flag": "^1.0.0" } }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", - "dev": true - }, "resolve-dir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-1.0.1.tgz", @@ -49769,21 +43724,18 @@ "value-or-function": "^3.0.0" } }, + "resolve-pkg-maps": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/resolve-pkg-maps/-/resolve-pkg-maps-1.0.0.tgz", + "integrity": "sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==", + "dev": true + }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==", "dev": true }, - "responselike": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", - "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", - "dev": true, - "requires": { - "lowercase-keys": "^3.0.0" - } - }, "resq": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/resq/-/resq-1.11.0.tgz", @@ -49879,9 +43831,9 @@ }, "dependencies": { "tslib": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", - "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", "dev": true } } @@ -50001,73 +43953,121 @@ } }, "send": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", - "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "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": "2.0.0", - "destroy": "1.2.0", + "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": "2.0.0", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "2.4.1", - "range-parser": "~1.2.1", - "statuses": "2.0.1" + "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" }, "dependencies": { "debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, "requires": { "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" - } } }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "dev": true + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg==", + "dev": true + }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "dev": true, + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "dev": true + }, "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==", + "dev": true }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "dev": true + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", + "dev": true, + "requires": { + "ee-first": "1.1.1" + } + }, + "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 } } }, "serialize-error": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-8.1.0.tgz", - "integrity": "sha512-3NnuWfM6vBYoy5gZFvHiYsVbafvI9vZv/+jlIigFn4oP4zjNPK3LhcY0xSCgeb1a5L8jO71Mit9LlNoi2UfDDQ==", + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/serialize-error/-/serialize-error-11.0.3.tgz", + "integrity": "sha512-2G2y++21dhj2R7iHAdd0FIzjGwuKZld+7Pl/bTU6YIkrC2ZMbVUjm+luj6A6V34Rv9XfKJDKpTWu9W4Gse1D9g==", "dev": true, "requires": { - "type-fest": "^0.20.2" + "type-fest": "^2.12.2" }, "dependencies": { "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==", + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", + "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", "dev": true } } }, "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", + "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", "dev": true, "requires": { "randombytes": "^2.1.0" @@ -50142,14 +44142,73 @@ } }, "serve-static": { - "version": "1.15.0", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", - "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "version": "1.16.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.2.tgz", + "integrity": "sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==", "requires": { - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "parseurl": "~1.3.3", - "send": "0.18.0" + "send": "0.19.0" + }, + "dependencies": { + "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" + }, + "dependencies": { + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + } + } + }, + "encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==" + }, + "mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "send": { + "version": "0.19.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.0.tgz", + "integrity": "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==", + "requires": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "dependencies": { + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + } + } + } } }, "set-blocking": { @@ -50232,6 +44291,15 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -50299,9 +44367,15 @@ } }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "slashes": { + "version": "3.0.12", + "resolved": "https://registry.npmjs.org/slashes/-/slashes-3.0.12.tgz", + "integrity": "sha512-Q9VME8WyGkc7pJf6QEkj3wE+2CnvZMI+XJhwdTPR8Z/kWQRXi7boAWLDibRPyHRTUTPx5FaU7MsyrjI3yLB4HA==", "dev": true }, "slice-ansi": { @@ -50572,12 +44646,21 @@ } }, "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.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", "dev": true, "requires": { - "source-map": "^0.5.6" + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } } }, "source-map-url": { @@ -51007,9 +45090,9 @@ "dev": true }, "strip-final-newline": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", - "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", + "integrity": "sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==", "dev": true }, "strip-json-comments": { @@ -51018,10 +45101,17 @@ "integrity": "sha512-0fk9zBqO67Nq5M/m45qHCJxylV/DhBlIOVExqgOMiCCrzrhU6tCibRXNqE3jwJLftzE9SNuZtYbpzcO+i9FiKw==", "dev": true }, + "strnum": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-1.0.5.tgz", + "integrity": "sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==", + "dev": true + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, "requires": { "has-flag": "^3.0.0" } @@ -51041,6 +45131,24 @@ "es6-symbol": "^3.1.1" } }, + "synckit": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/synckit/-/synckit-0.9.0.tgz", + "integrity": "sha512-7RnqIMq572L8PeEzKeBINYEJDDxpcH8JEgLwUqBd3TkofhFRbkq4QLR0u+36avGAhCRbk2nnmjcW9SE531hPDg==", + "dev": true, + "requires": { + "@pkgr/core": "^0.1.0", + "tslib": "^2.6.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "dev": true + } + } + }, "table": { "version": "6.8.2", "resolved": "https://registry.npmjs.org/table/-/table-6.8.2.tgz", @@ -51186,22 +45294,6 @@ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "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.21", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", - "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "source-map": "^0.6.0" - } } } }, @@ -51348,13 +45440,13 @@ "dev": true }, "timers-ext": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.7.tgz", - "integrity": "sha512-b85NUNzTSdodShTIbky6ZF02e8STtVVfD+fu4aXXShEELpozH+bCpJLYMPZbsABN2wDH7fJpqIoXxJpzbf0NqQ==", + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/timers-ext/-/timers-ext-0.1.8.tgz", + "integrity": "sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==", "dev": true, "requires": { - "es5-ext": "~0.10.46", - "next-tick": "1" + "es5-ext": "^0.10.64", + "next-tick": "^1.1.0" } }, "tiny-hashes": { @@ -51387,6 +45479,12 @@ } } }, + "tinyrainbow": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz", + "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==", + "dev": true + }, "tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -51403,11 +45501,6 @@ "is-negated-glob": "^1.0.0" } }, - "to-fast-properties": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", - "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==" - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", @@ -51509,12 +45602,6 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "dev": true - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", @@ -51527,12 +45614,6 @@ "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", "dev": true }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha512-WZGXGstmCWgeevgTL54hrCuw1dyMQIzWy7ZfqRJfSmJZBwklI15egmQytFP6bPidmw3M8d5yEowl1niq4vmqZw==", - "dev": true - }, "triple-beam": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/triple-beam/-/triple-beam-1.4.1.tgz", @@ -51579,6 +45660,17 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "dev": true }, + "tsx": { + "version": "4.17.0", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.17.0.tgz", + "integrity": "sha512-eN4mnDA5UMKDt4YZixo9tBioibaMBpoxBkD+rIPAjVmYERSG0/dWEY1CEFuV89CgASlKL499q8AhmkMnnjtOJg==", + "dev": true, + "requires": { + "esbuild": "~0.23.0", + "fsevents": "~2.3.3", + "get-tsconfig": "^4.7.5" + } + }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -51716,9 +45808,9 @@ "dev": true }, "uglify-js": { - "version": "3.17.4", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.17.4.tgz", - "integrity": "sha512-T9q82TJI9e/C1TAxYvfb16xO120tMVFZrGA3f9/P4424DNu6ypK103y0GPFVa17yotwSyZW5iYXgjYHkGrJW/g==", + "version": "3.18.0", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.18.0.tgz", + "integrity": "sha512-SyVVbcNBCk0dzr9XL/R/ySrmYf0s372K6/hFklzgcp2lBFyXtw4I7BOdDjlLhE1aVqaI/SHWXWmYdlZxuyF38A==", "dev": true, "optional": true }, @@ -51782,6 +45874,12 @@ "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", "dev": true }, + "undici": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.21.0.tgz", + "integrity": "sha512-BUgJXc752Kou3oOIuU1i+yZZypyZRqNPW0vqoMPl8VaoalSfeR0D8/t4iAS3yirs79SSMTxTag+ZC86uswv+Cw==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -51812,6 +45910,12 @@ "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-2.1.0.tgz", "integrity": "sha512-6t3foTQI9qne+OZoVQB/8x8rk2k1eVy1gRXhV3oFQ5T6R1dqQ1xtin3XqSlx3+ATBkliTaR/hHyJBm+LVPNM8w==" }, + "unicorn-magic": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz", + "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==", + "dev": true + }, "unified": { "version": "10.1.2", "resolved": "https://registry.npmjs.org/unified/-/unified-10.1.2.tgz", @@ -52020,12 +46124,12 @@ "dev": true }, "update-browserslist-db": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz", - "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.1.tgz", + "integrity": "sha512-R8UzCaa9Az+38REPiJ1tXlImTJXlVfgHZsglwBD/k6nj76ctsH1E3q4doGrukiLQd3sGQYu56r5+lo5r94l29A==", "requires": { - "escalade": "^3.1.2", - "picocolors": "^1.0.1" + "escalade": "^3.2.0", + "picocolors": "^1.1.0" } }, "uri-js": { @@ -52086,6 +46190,12 @@ "integrity": "sha512-mtN6xk+Nac+oyJ/PrI7tzfmomRVNFIWKUbG8jdYFt52hxbiReFAXIjYskvu64/dvuW71IcB7lV8l0HvZMac6Jg==", "dev": true }, + "urlpattern-polyfill": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-10.0.0.tgz", + "integrity": "sha512-H/A06tKD7sS1O1X2SshBVeA5FLycRpjqiBeqGKmBwBDBy28EnRjORxTNe269KSSr5un5qyWi1iL61wLxpd+ZOg==", + "dev": true + }, "use": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", @@ -52122,9 +46232,9 @@ "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==" }, "uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "dev": true }, "uvu": { @@ -52336,17 +46446,15 @@ "dev": true }, "videojs-ima": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-1.11.0.tgz", - "integrity": "sha512-ZRoWuGyJ75zamwZgpr0i/gZ6q7Evda/Q6R46gpW88WN7u0ORU7apw/lM1MSG4c3YDXW8LDENgzMAvMZUdifWhg==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/videojs-ima/-/videojs-ima-2.3.0.tgz", + "integrity": "sha512-8r0BZGT+WCTO6PePyKZHikV79Ojqh4yLMx4+DmPyXeRcKUVsQ7Va0R7Ok8GRcA8Zy3l1PM6jzLrD/W1rwKhZ8g==", "dev": true, "requires": { "@hapi/cryptiles": "^5.1.0", - "can-autoplay": "^3.0.0", + "can-autoplay": "^3.0.2", "extend": ">=3.0.2", - "lodash": ">=4.17.19", - "lodash.template": ">=4.5.0", - "videojs-contrib-ads": "^6.6.5" + "videojs-contrib-ads": "^6.9.0" } }, "videojs-playlist": { @@ -52585,6 +46693,7 @@ "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", "dev": true, + "optional": true, "requires": { "defaults": "^1.0.3" } @@ -52602,383 +46711,368 @@ "dev": true }, "webdriver": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.36.1.tgz", - "integrity": "sha512-547RivYCHStVqtiGQBBcABAkzJbPnAWsxpXGzmj5KL+TOM2JF41N2iQRtUxXqr0jme1Nzzye7WS7Y7iSnK6i1g==", + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.4.1.tgz", + "integrity": "sha512-vFDdxMj/9W1+6jhpHSiRYfO8dix23HjAUtLx7aOv9ejEsntC0EzCIAftJ59YsF3Ppu184+FkdDVhnivpkZPTFw==", "dev": true, "requires": { "@types/node": "^20.1.0", "@types/ws": "^8.5.3", - "@wdio/config": "8.36.1", - "@wdio/logger": "8.28.0", - "@wdio/protocols": "8.32.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", - "deepmerge-ts": "^5.1.0", - "got": "^12.6.1", - "ky": "^0.33.0", + "@wdio/config": "9.2.8", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "deepmerge-ts": "^7.0.3", + "undici": "^6.20.1", "ws": "^8.8.0" - } - }, - "webdriverio": { - "version": "7.36.0", - "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-7.36.0.tgz", - "integrity": "sha512-OTYmKBF7eFKBX39ojUIEzw7AlE1ZRJiFoMTtEQaPMuPzZCP2jUBq6Ey38nuZrYXLkXn3/le9a14pNnKSM0n56w==", - "dev": true, - "requires": { - "@types/aria-query": "^5.0.0", - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/repl": "7.33.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "archiver": "^5.0.0", - "aria-query": "^5.2.1", - "css-shorthand-properties": "^1.1.1", - "css-value": "^0.0.1", - "devtools": "7.35.0", - "devtools-protocol": "^0.0.1260888", - "fs-extra": "^11.1.1", - "grapheme-splitter": "^1.0.2", - "lodash.clonedeep": "^4.5.0", - "lodash.isobject": "^3.0.2", - "lodash.isplainobject": "^4.0.6", - "lodash.zip": "^4.2.0", - "minimatch": "^6.0.4", - "puppeteer-core": "^13.1.3", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", - "rgb2hex": "0.2.5", - "serialize-error": "^8.0.0", - "webdriver": "7.33.0" }, "dependencies": { - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "dev": true, - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@types/node": { - "version": "18.19.33", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.33.tgz", - "integrity": "sha512-NR9+KrpSajr2qBVp/Yt5TU/rp+b5Mayi3+OlMlcg2cVCfRmcG5PWZ7S4+MG9PZ5gWBoc9Pd0BKSRViuBCRPu0A==", - "dev": true, - "requires": { - "undici-types": "~5.26.4" - } - }, - "@wdio/config": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-7.33.0.tgz", - "integrity": "sha512-SaCZNKrDtBghf7ujyaxTiU4pBW+1Kms32shSoXpJ/wFop6/MiA7nb19qpUPoJtEDw5/NOKevUKz8nBMBXphiew==", + "@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", "dev": true, "requires": { - "@types/glob": "^8.1.0", - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "deepmerge": "^4.0.0", - "glob": "^8.0.3" + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } }, "@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "requires": { - "chalk": "^4.0.0", + "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - } - }, - "@wdio/protocols": { - "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-7.27.0.tgz", - "integrity": "sha512-hT/U22R5i3HhwPjkaKAG0yd59eaOaZB0eibRj2+esCImkb5Y6rg8FirrlYRxIGFVBl0+xZV0jKHzR5+o097nvg==", - "dev": true - }, - "@wdio/repl": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-7.33.0.tgz", - "integrity": "sha512-17KM9NCg+UVpZNbS8koT/917vklF5M8IQnw0kGwmJEo444ifTMxmLwQymbS2ovQKAKAQxlfdM7bpqMeI15kzsQ==", - "dev": true, - "requires": { - "@wdio/utils": "7.33.0" + "strip-ansi": "^7.1.0" } }, "@wdio/types": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-7.33.0.tgz", - "integrity": "sha512-tNcuN5Kl+i5CffaeTYV1omzAo4rVjiI1m9raIA8ph6iVteWdCzYv2/ImpGgFiBPb7Mf6VokU3+q9Slh5Jitaww==", + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "requires": { - "@types/node": "^18.0.0", - "got": "^11.8.1" + "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-7.33.0.tgz", - "integrity": "sha512-4kQQ86EvEN6fBY5+u7M08cT6LfJtpk1rHd203xyxmbmV9lpNv/OCl4CsC+SD0jGT0aZZqYSIJ/Pil07pAh5K0g==", - "dev": true, - "requires": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "color-convert": "^2.0.1" + "debug": "^4.3.4" } }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true }, - "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==", + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true }, - "cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "4" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", + "dev": true }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { - "color-name": "~1.1.4" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true }, - "fs-extra": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.2.0.tgz", - "integrity": "sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==", + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "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==", + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { - "pump": "^3.0.0" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + } + } + } + }, + "webdriverio": { + "version": "9.4.1", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.4.1.tgz", + "integrity": "sha512-XIPtRnxSES4CoxH2BfUY/6QzNgEgJEUjMYu7t7SJR8bVfbLRVXAA1ie9kM0MtdLs4oZ2Pr8rR8fqytsA1CjEWw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.2.8", + "@wdio/logger": "9.1.3", + "@wdio/protocols": "9.2.2", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.2.2", + "@wdio/utils": "9.2.8", + "archiver": "^7.0.1", + "aria-query": "^5.3.0", + "cheerio": "^1.0.0-rc.12", + "css-shorthand-properties": "^1.1.1", + "css-value": "^0.0.1", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.3.0", + "import-meta-resolve": "^4.0.0", + "is-plain-obj": "^4.1.0", + "jszip": "^3.10.1", + "lodash.clonedeep": "^4.5.0", + "lodash.zip": "^4.2.0", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", + "rgb2hex": "0.2.5", + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.4.1" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.5.0.tgz", + "integrity": "sha512-6TQAc/5uRILE6deixJ1CR8rXyTbzXIXNgO1D0Woi9Bqicz2FV5iKP3BHYEg6o4UATCMcbQQ0jbmeaOkn/HQk2w==", + "dev": true, + "requires": { + "debug": "^4.3.7", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.4.0", + "semver": "^7.6.3", + "tar-fs": "^3.0.6", + "unbzip2-stream": "^1.4.3", + "yargs": "^17.7.2" } }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "@wdio/logger": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.1.3.tgz", + "integrity": "sha512-cumRMK/gE1uedBUw3WmWXOQ7HtB6DR8EyKQioUz2P0IJtRRpglMBdZV7Svr3b++WWawOuzZHMfbTkJQmaVt8Gw==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" - }, - "dependencies": { - "minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - } - } + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "@wdio/types": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.2.2.tgz", + "integrity": "sha512-nHZ9Ne9iRQFJ1TOYKUn4Fza69IshTTzk6RYmSZ51ImGs9uMZu0+S0Jm9REdly+VLN3FzxG6g2QSe0/F3uNVPdw==", "dev": true, "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "@types/node": "^20.1.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 + "@wdio/utils": { + "version": "9.2.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.2.8.tgz", + "integrity": "sha512-rKm5FXkpsCyeqh8tdirtRUHvgNytWNMiaVKdctsvKOJvqnDVPAAQcz9Wmgo7bSwoLwtSHcDaRoxY7olV7J4QnA==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.1.3", + "@wdio/types": "9.2.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "edgedriver": "^5.6.1", + "geckodriver": "^4.3.3", + "get-port": "^7.0.0", + "import-meta-resolve": "^4.0.0", + "locate-app": "^2.2.24", + "safaridriver": "^0.1.2", + "split2": "^4.2.0", + "wait-port": "^1.1.0" + } }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", + "agent-base": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", + "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", "dev": true, "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "debug": "^4.3.4" } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "dev": true, "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "balanced-match": "^1.0.0" } }, - "ky": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", - "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "dev": true }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "deepmerge-ts": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.3.tgz", + "integrity": "sha512-qCSH6I0INPxd9Y1VtAiLpnYvz5O//6rCfJXKk0z66Up9/VOSr+1yS8XSKA5IWRxjocFGlzPyaZYe+jxq7OOLtQ==", "dev": true }, - "minimatch": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "https-proxy-agent": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.5.tgz", + "integrity": "sha512-1e4Wqeblerz+tMKPIq2EMGiiWW1dIjZOksyHWSUm1rmuvw/how9hBHZ38lAGj5ID4Ik6EdkOw7NmWPy6LAwalw==", "dev": true, "requires": { - "brace-expansion": "^2.0.1" + "agent-base": "^7.0.2", + "debug": "4" } }, - "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 - }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "lru-cache": { + "version": "7.18.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", + "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", "dev": true }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { - "lowercase-keys": "^2.0.0" + "brace-expansion": "^2.0.1" } }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "proxy-agent": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", + "integrity": "sha512-u0piLU+nCOHMgGjRbimiXmA9kM/L9EHh3zL81xCdp7m+Y2pHIsnmbdDoEDoAz5geaonNR6q6+yOPQs6n4T6sBQ==", "dev": true, "requires": { - "ansi-regex": "^5.0.1" + "agent-base": "^7.0.2", + "debug": "^4.3.4", + "http-proxy-agent": "^7.0.1", + "https-proxy-agent": "^7.0.3", + "lru-cache": "^7.14.1", + "pac-proxy-agent": "^7.0.1", + "proxy-from-env": "^1.1.0", + "socks-proxy-agent": "^8.0.2" } }, - "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==", + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "dev": true + }, + "tar-fs": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.6.tgz", + "integrity": "sha512-iokBDQQkUyeXhgPYaZxmczGPhnhXZ0CmrqI+MOb/WFGS9DW5wnfrLgtjUJBvz50vQ3qfRwJ62QVoCFu8mPVu5w==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "webdriver": { - "version": "7.33.0", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-7.33.0.tgz", - "integrity": "sha512-cyMRAVUHgQhEBHojOeNet2e8GkfyvvjpioNCPcF6qUtT+URdagr8Mh0t4Fs+Jr0tpuMqFnw70xZexAcV/6I/jg==", + "yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "requires": { - "@types/node": "^18.0.0", - "@wdio/config": "7.33.0", - "@wdio/logger": "7.26.0", - "@wdio/protocols": "7.27.0", - "@wdio/types": "7.33.0", - "@wdio/utils": "7.33.0", - "got": "^11.0.2", - "ky": "0.30.0", - "lodash.merge": "^4.6.1" + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" } } } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "dev": true - }, "webpack": { "version": "5.91.0", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.91.0.tgz", @@ -53094,9 +47188,9 @@ "dev": true }, "ws": { - "version": "7.5.9", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", - "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "dev": true, "requires": {} } @@ -53238,16 +47332,32 @@ "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", "dev": true }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", "dev": true, "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "iconv-lite": "0.6.3" + }, + "dependencies": { + "iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "dev": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + } + } } }, + "whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true + }, "which": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", @@ -53338,9 +47448,9 @@ "dev": true }, "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", + "version": "6.5.1", + "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.5.1.tgz", + "integrity": "sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA==", "dev": true }, "wrap-ansi": { @@ -53534,58 +47644,59 @@ "integrity": "sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==", "dev": true }, + "yoctocolors": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.1.tgz", + "integrity": "sha512-GQHQqAopRhwU8Kt1DDM8NjibDXHC8eoh1erhGAJPEyveY9qqVeXvVikNKrDz69sHowPMorbPUrH/mx8c50eiBQ==", + "dev": true + }, + "yoctocolors-cjs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz", + "integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==", + "dev": true + }, "zip-stream": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-4.1.1.tgz", - "integrity": "sha512-9qv4rlDiopXg4E69k+vMHjNN63YFMe9sZMrdlvKnCjlCRWeCBswPPMPUfx+ipsAWq1LXHe70RcbaHdJJpS6hyQ==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz", + "integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==", "dev": true, "requires": { - "archiver-utils": "^3.0.4", - "compress-commons": "^4.1.2", - "readable-stream": "^3.6.0" + "archiver-utils": "^5.0.0", + "compress-commons": "^6.0.2", + "readable-stream": "^4.0.0" }, "dependencies": { - "archiver-utils": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-3.0.4.tgz", - "integrity": "sha512-KVgf4XQVrTjhyWmx6cte4RxonPLR9onExufI1jhvw/MQ4BB6IsZD5gT8Lq+u/+pRkWna/6JoHpiQioaqFP5Rzw==", + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "dev": true, "requires": { - "glob": "^7.2.3", - "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": "^3.6.0" + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, - "glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "readable-stream": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.5.2.tgz", + "integrity": "sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" } }, - "readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "dev": true, "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "safe-buffer": "~5.2.0" } } } diff --git a/package.json b/package.json index b72e1bf6ba3..71be6d1c243 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { "name": "prebid.js", - "version": "8.50.1", + "version": "9.22.0", "description": "Header Bidding Management Library", - "main": "src/prebid.js", + "main": "src/prebid.public.js", + "exports": { + ".": "./src/prebid.public.js", + "./prebid.js": "./src/prebid.public.js", + "./prebid": "./src/prebid.public.js", + "./.babelrc.js": "./.babelrc.js", + "./babelConfig.js": "./babelConfig.js", + "./modules/*": "./modules/*", + "./modules/*.js": "./modules/*.js" + }, "scripts": { "serve": "gulp serve", "test": "gulp test", @@ -31,25 +40,25 @@ ], "globalVarName": "fsprebid", "defineGlobal": true, - "author": "the prebid.js contributors", + "author": "The prebid.js contributors", "license": "Apache-2.0", "engines": { - "node": ">=12.0.0" + "node": ">=20.0.0" }, "devDependencies": { "@babel/eslint-parser": "^7.16.5", "@babel/plugin-proposal-private-methods": "^7.18.6", - "@wdio/browserstack-service": "^8.29.0", - "@wdio/cli": "^8.29.0", + "@babel/register": "^7.24.6", + "@wdio/browserstack-service": "^9.0.5", + "@wdio/cli": "^9.0.5", "@wdio/concise-reporter": "^8.29.0", - "@wdio/local-runner": "^8.29.0", + "@wdio/local-runner": "^9.0.5", "@wdio/mocha-framework": "^8.29.0", "@wdio/spec-reporter": "^8.29.0", "ajv": "6.12.3", "assert": "^2.0.0", "babel-loader": "^8.0.5", "babel-plugin-istanbul": "^6.1.1", - "babel-register": "^6.26.0", "body-parser": "^1.19.0", "chai": "^4.2.0", "coveralls": "^3.1.0", @@ -58,8 +67,8 @@ "es5-shim": "^4.5.14", "eslint": "^7.27.0", "eslint-config-standard": "^10.2.1", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jsdoc": "^38.1.6", + "eslint-plugin-import": "^2.30.0", + "eslint-plugin-jsdoc": "^48.5.0", "eslint-plugin-node": "^11.1.0", "eslint-plugin-prebid": "file:./plugins/eslint", "eslint-plugin-promise": "^5.1.0", @@ -102,7 +111,7 @@ "karma-spec-reporter": "^0.0.32", "karma-webpack": "^5.0.0", "lodash": "^4.17.21", - "mocha": "^10.0.0", + "mocha": "^10.7.3", "morgan": "^1.10.0", "node-html-parser": "^6.1.5", "opn": "^5.4.0", @@ -113,9 +122,9 @@ "url-parse": "^1.0.5", "video.js": "^7.17.0", "videojs-contrib-ads": "^6.9.0", - "videojs-ima": "^1.11.0", + "videojs-ima": "^2.3.0", "videojs-playlist": "^5.0.0", - "webdriverio": "^7.6.1", + "webdriverio": "^9.0.9", "webpack": "^5.70.0", "webpack-bundle-analyzer": "^4.5.0", "webpack-manifest-plugin": "^5.0.0", @@ -123,21 +132,20 @@ "yargs": "^1.3.1" }, "dependencies": { - "@babel/core": "^7.16.7", + "@babel/core": "^7.25.2", "@babel/plugin-transform-runtime": "^7.18.9", "@babel/preset-env": "^7.16.8", "@babel/runtime": "^7.18.9", "core-js": "^3.13.0", "core-js-pure": "^3.13.0", - "criteo-direct-rsa-validate": "^1.1.0", "crypto-js": "^4.2.0", "dlv": "1.1.3", - "dset": "3.1.2", + "dset": "3.1.4", "express": "^4.15.4", "fun-hooks": "^0.9.9", "gulp-wrap": "^0.15.0", "klona": "^2.0.6", - "live-connect-js": "^6.3.4" + "live-connect-js": "^7.1.0" }, "optionalDependencies": { "fsevents": "^2.3.2" diff --git a/plugins/eslint/index.js b/plugins/eslint/index.js new file mode 100644 index 00000000000..f8acf54683b --- /dev/null +++ b/plugins/eslint/index.js @@ -0,0 +1,104 @@ +const _ = require('lodash'); +const { flagErrors } = require('./validateImports.js'); +const noGlobal = require('eslint/lib/rules/no-restricted-globals.js'); + +function getName(node) { + return node.type === 'Literal' ? node.value : node.name; +} + +module.exports = { + rules: { + 'validate-imports': { + meta: { + docs: { + description: 'validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points' + } + }, + create: function(context) { + return { + "CallExpression[callee.name='require']"(node) { + let importPath = _.get(node, ['arguments', 0, 'value']); + if (importPath) { + flagErrors(context, node, importPath); + } + }, + ImportDeclaration(node) { + let importPath = node.source.value.trim(); + flagErrors(context, node, importPath); + }, + 'ExportNamedDeclaration[source]'(node) { + let importPath = node.source.value.trim(); + flagErrors(context, node, importPath); + } + }; + } + }, + 'no-member': { + meta: { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + target: { type: 'string' }, + name: { type: 'string' }, + message: { type: 'string' } + }, + required: ['name', 'message'], + additionalProperties: false + }, + uniqueItems: true, + minItems: 1 + }, + + messages: { + noMember: "Unexpected use of '{{target}}.{{name}}'. {{message}}", + } + }, + + create(context) { + return { + MemberExpression(node) { + context.options.forEach(({name, target, message}) => { + if (target === node.object.name && getName(node.property) === name) { + context.report({ + node, + messageId: 'noMember', + data: { + name, + target: target || '', + message + } + }); + } + }); + } + } + } + }, + 'no-global': Object.assign({}, noGlobal, { + // no-restricted-global that also looks for `window.GLOBAL` + create(context) { + const globals = Object.fromEntries( + context.options.map(option => typeof option === 'string' ? [option, null] : [option.name, option.message]) + ) + return Object.assign(noGlobal.create(context), { + MemberExpression(node) { + const name = getName(node.property); + if (node.object.name === 'window' && globals.hasOwnProperty(name)) { + const customMessage = globals[name]; + context.report({ + node, + messageId: customMessage == null ? 'defaultMessage' : 'customMessage', + data: { + name, + customMessage + } + }) + } + } + }) + } + }), + } +}; diff --git a/plugins/eslint/package.json b/plugins/eslint/package.json index fa18ad83718..446f63945fa 100644 --- a/plugins/eslint/package.json +++ b/plugins/eslint/package.json @@ -2,7 +2,7 @@ "name": "eslint-plugin-prebid", "version": "1.0.0", "description": "validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points", - "main": "validateImports.js", + "main": "index.js", "author": "the prebid.js contributors", "license": "Apache-2.0" } diff --git a/plugins/eslint/validateImports.js b/plugins/eslint/validateImports.js index b936f44aee7..e38f0532238 100644 --- a/plugins/eslint/validateImports.js +++ b/plugins/eslint/validateImports.js @@ -53,31 +53,5 @@ function flagErrors(context, node, importPath) { } module.exports = { - rules: { - 'validate-imports': { - meta: { - docs: { - description: 'validates module imports can be found without custom webpack resolvers, are in module whitelist, and not module entry points' - } - }, - create: function(context) { - return { - "CallExpression[callee.name='require']"(node) { - let importPath = _.get(node, ['arguments', 0, 'value']); - if (importPath) { - flagErrors(context, node, importPath); - } - }, - ImportDeclaration(node) { - let importPath = node.source.value.trim(); - flagErrors(context, node, importPath); - }, - 'ExportNamedDeclaration[source]'(node) { - let importPath = node.source.value.trim(); - flagErrors(context, node, importPath); - } - } - } - } - } -}; + flagErrors +} diff --git a/src/Renderer.js b/src/Renderer.js index 2f9b2e025cb..912259206c4 100644 --- a/src/Renderer.js +++ b/src/Renderer.js @@ -4,6 +4,7 @@ import { } from './utils.js'; import {find} from './polyfill.js'; import {getGlobal} from './prebidGlobal.js'; +import { MODULE_TYPE_PREBID } from './activities/modules.js'; const pbjsInstance = getGlobal(); const moduleCode = 'outstream'; @@ -62,7 +63,7 @@ export function Renderer(options) { } else { // 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, this.documentContext); + loadExternalScript(url, MODULE_TYPE_PREBID, moduleCode, this.callback, this.documentContext); } }.bind(this); // bind the function to this object to avoid 'this' errors } diff --git a/src/activities/activities.js b/src/activities/activities.js index 0a17750b0b0..40f43fb9114 100644 --- a/src/activities/activities.js +++ b/src/activities/activities.js @@ -50,3 +50,8 @@ export const ACTIVITY_TRANSMIT_PRECISE_GEO = 'transmitPreciseGeo'; * transmit TID: some component wants access ot (and send along) transaction IDs */ export const ACTIVITY_TRANSMIT_TID = 'transmitTid'; + +/** + * loadExternalScript: adLoader.js is allowed to load external script + */ +export const LOAD_EXTERNAL_SCRIPT = 'loadExternalScript'; diff --git a/src/activities/params.js b/src/activities/params.js index 036a6657cf8..859f5d5beed 100644 --- a/src/activities/params.js +++ b/src/activities/params.js @@ -40,8 +40,10 @@ export const ACTIVITY_PARAM_SYNC_TYPE = 'syncType' export const ACTIVITY_PARAM_SYNC_URL = 'syncUrl'; /** * @private - * configuration options for analytics adapter - the argument passed to `enableAnalytics`. - * relevant for: reportAnalytics + * Configuration options for analytics adapter - the argument passed to `enableAnalytics`. + * Relevant for: reportAnalytics. + * @constant + * @type {string} */ export const ACTIVITY_PARAM_ANL_CONFIG = '_config'; diff --git a/src/activities/redactor.js b/src/activities/redactor.js index 694a96b2b14..65d14722ce5 100644 --- a/src/activities/redactor.js +++ b/src/activities/redactor.js @@ -7,6 +7,7 @@ import { ACTIVITY_TRANSMIT_TID, ACTIVITY_TRANSMIT_UFPD } from './activities.js'; +import { scrubIPv4, scrubIPv6 } from '../utils/ipUtils.js'; export const ORTB_UFPD_PATHS = [ 'data', @@ -21,6 +22,8 @@ export const ORTB_UFPD_PATHS = [ ].map(f => `user.${f}`).concat('device.ext.cdep'); export const ORTB_EIDS_PATHS = ['user.eids', 'user.ext.eids']; export const ORTB_GEO_PATHS = ['user.geo.lat', 'user.geo.lon', 'device.geo.lat', 'device.geo.lon']; +export const ORTB_IPV4_PATHS = ['device.ip'] +export const ORTB_IPV6_PATHS = ['device.ipv6'] /** * @typedef TransformationRuleDef @@ -157,6 +160,22 @@ export function ortb2TransmitRules(isAllowed = isActivityAllowed) { return Math.round((val + Number.EPSILON) * 100) / 100; } }, + { + name: ACTIVITY_TRANSMIT_PRECISE_GEO, + paths: ORTB_IPV4_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_PRECISE_GEO, isAllowed), + get(val) { + return scrubIPv4(val); + } + }, + { + name: ACTIVITY_TRANSMIT_PRECISE_GEO, + paths: ORTB_IPV6_PATHS, + applies: appliesWhenActivityDenied(ACTIVITY_TRANSMIT_PRECISE_GEO, isAllowed), + get(val) { + return scrubIPv6(val); + } + }, { name: ACTIVITY_TRANSMIT_TID, paths: ['source.tid'], diff --git a/src/activities/rules.js b/src/activities/rules.js index f84f1080843..7b4f4634f07 100644 --- a/src/activities/rules.js +++ b/src/activities/rules.js @@ -40,19 +40,19 @@ export function ruleRegistry(logger = prefixLog('Activity control:')) { /** * Register an activity control rule. * - * @param {string} activity activity name - set is defined in `activities.js` - * @param {string} ruleName a name for this rule; used for logging. - * @param {function({}): {allow: boolean, reason?: string}} rule definition function. Takes in activity + * @param {string} activity - Activity name, as defined in `activities.js`. + * @param {string} ruleName - A name for this rule, used for logging. + * @param {function(Object): {allow: boolean, reason?: string}} rule - Rule definition function. Takes in activity * parameters as a single map; MAY return an object {allow, reason}, where allow is true/false, * and reason is an optional message used for logging. * - * {allow: true} will allow this activity AS LONG AS no other rules with same or higher priority return {allow: false}; + * {allow: true} will allow this activity AS LONG AS no other rules with the same or higher priority return {allow: false}; * {allow: false} will deny this activity AS LONG AS no other rules with higher priority return {allow: true}; - * returning null/undefined has no effect - the decision is left to other rules. + * Returning null/undefined has no effect - the decision is left to other rules. * If no rule returns an allow value, the default is to allow the activity. * - * @param {number} priority rule priority; lower number means higher priority - * @returns {function(void): void} a function that unregisters the rule when called. + * @param {number} [priority=10] - Rule priority; lower number means higher priority. + * @returns {function(): void} - A function that unregisters the rule when called. */ function registerActivityControl(activity, ruleName, rule, priority = 10) { const rules = getRules(activity); diff --git a/src/adRendering.js b/src/adRendering.js index 7d306adc9cc..130ee74278d 100644 --- a/src/adRendering.js +++ b/src/adRendering.js @@ -1,6 +1,15 @@ -import {createIframe, deepAccess, inIframe, insertElement, logError, logWarn, replaceMacros} from './utils.js'; +import { + createIframe, + createInvisibleIframe, + deepAccess, + inIframe, + insertElement, + logError, + logWarn, + replaceMacros +} from './utils.js'; import * as events from './events.js'; -import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS, MESSAGES } from './constants.js'; +import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS, MESSAGES, PB_LOCATOR} from './constants.js'; import {config} from './config.js'; import {executeRenderer, isRendererRequired} from './Renderer.js'; import {VIDEO} from './mediaTypes.js'; @@ -8,10 +17,25 @@ import {auctionManager} from './auctionManager.js'; import {getCreativeRenderer} from './creativeRenderers.js'; import {hook} from './hook.js'; import {fireNativeTrackers} from './native.js'; +import {GreedyPromise} from './utils/promise.js'; +import adapterManager from './adapterManager.js'; +import {useMetrics} from './utils/perfMetrics.js'; +import {filters} from './targeting.js'; -const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON } = EVENTS; +const { AD_RENDER_FAILED, AD_RENDER_SUCCEEDED, STALE_RENDER, BID_WON, EXPIRED_RENDER } = EVENTS; const { EXCEPTION } = AD_RENDER_FAILED_REASON; +export const getBidToRender = hook('sync', function (adId, forRender = true, override = GreedyPromise.resolve()) { + return override + .then(bid => bid ?? auctionManager.findBidByAdId(adId)) + .catch(() => {}) +}) + +export const markWinningBid = hook('sync', function (bid) { + events.emit(BID_WON, bid); + auctionManager.addWinningBid(bid); +}) + /** * Emit the AD_RENDER_FAILED event. * @@ -47,6 +71,8 @@ export function emitAdRenderSucceeded({ doc, bid, id }) { if (bid) data.bid = bid; if (id) data.adId = id; + adapterManager.callAdRenderSucceededBidder(bid.adapterCode || bid.bidder, bid); + events.emit(AD_RENDER_SUCCEEDED, data); } @@ -112,11 +138,12 @@ export const getRenderingData = hook('sync', function (bidResponse, options) { }; }) -export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options}) { - if (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) { +export const doRender = hook('sync', function({renderFn, resizeFn, bidResponse, options, doc, isMainDocument = doc === document && !inIframe()}) { + const videoBid = (FEATURES.VIDEO && bidResponse.mediaType === VIDEO) + if (isMainDocument || videoBid) { emitAdRenderFail({ reason: AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, - message: 'Cannot render video ad', + message: videoBid ? 'Cannot render video ad without a renderer' : `renderAd was prevented from writing to the main document.`, bid: bidResponse, id: bidResponse.adId }); @@ -143,33 +170,82 @@ doRender.before(function (next, args) { }, 100) export function handleRender({renderFn, resizeFn, adId, options, bidResponse, doc}) { + deferRendering(bidResponse, () => { + if (bidResponse == null) { + emitAdRenderFail({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + message: `Cannot find ad '${adId}'`, + id: adId + }); + return; + } + if (bidResponse.status === BID_STATUS.RENDERED) { + logWarn(`Ad id ${adId} has been rendered before`); + events.emit(STALE_RENDER, bidResponse); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { + return; + } + } + if (!filters.isBidNotExpired(bidResponse)) { + logWarn(`Ad id ${adId} has been expired`); + events.emit(EXPIRED_RENDER, bidResponse); + if (deepAccess(config.getConfig('auctionOptions'), 'suppressExpiredRender')) { + return; + } + } + + try { + doRender({renderFn, resizeFn, bidResponse, options, doc}); + } catch (e) { + emitAdRenderFail({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, + message: e.message, + id: adId, + bid: bidResponse + }); + } + }) +} + +export function markBidAsRendered(bidResponse) { + const metrics = useMetrics(bidResponse.metrics); + metrics.checkpoint('bidRender'); + metrics.timeBetween('bidWon', 'bidRender', 'render.deferred'); + metrics.timeBetween('auctionEnd', 'bidRender', 'render.pending'); + metrics.timeBetween('requestBids', 'bidRender', 'render.e2e'); + bidResponse.status = BID_STATUS.RENDERED; +} + +const DEFERRED_RENDER = new WeakMap(); +const WINNERS = new WeakSet(); + +export function deferRendering(bidResponse, renderFn) { if (bidResponse == null) { - emitAdRenderFail({ - reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - message: `Cannot find ad '${adId}'`, - id: adId - }); + // if the bid is missing, let renderFn deal with it now + renderFn(); return; } - if (bidResponse.status === BID_STATUS.RENDERED) { - logWarn(`Ad id ${adId} has been rendered before`); - events.emit(STALE_RENDER, bidResponse); - if (deepAccess(config.getConfig('auctionOptions'), 'suppressStaleRender')) { - return; - } + DEFERRED_RENDER.set(bidResponse, renderFn); + if (!bidResponse.deferRendering) { + renderIfDeferred(bidResponse); } - try { - doRender({renderFn, resizeFn, bidResponse, options, doc}); - } catch (e) { - emitAdRenderFail({ - reason: AD_RENDER_FAILED_REASON.EXCEPTION, - message: e.message, - id: adId, - bid: bidResponse - }); + markWinner(bidResponse); +} + +export function markWinner(bidResponse) { + if (!WINNERS.has(bidResponse)) { + WINNERS.add(bidResponse); + markWinningBid(bidResponse); + } +} + +export function renderIfDeferred(bidResponse) { + const renderFn = DEFERRED_RENDER.get(bidResponse); + if (renderFn) { + renderFn(); + markBidAsRendered(bidResponse); + DEFERRED_RENDER.delete(bidResponse); } - auctionManager.addWinningBid(bidResponse); - events.emit(BID_WON, bidResponse); } export function renderAdDirect(doc, adId, options) { @@ -188,7 +264,7 @@ export function renderAdDirect(doc, adId, options) { if (adData.ad) { doc.write(adData.ad); doc.close(); - emitAdRenderSucceeded({doc, bid, adId: bid.adId}); + emitAdRenderSucceeded({doc, bid, id: bid.adId}); } else { getCreativeRenderer(bid) .then(render => render(adData, { @@ -196,7 +272,7 @@ export function renderAdDirect(doc, adId, options) { mkFrame: createIframe, }, doc.defaultView)) .then( - () => emitAdRenderSucceeded({doc, bid, adId: bid.adId}), + () => emitAdRenderSucceeded({doc, bid, id: bid.adId}), (e) => { fail(e?.reason || AD_RENDER_FAILED_REASON.EXCEPTION, e?.message) e?.stack && logError(e); @@ -211,15 +287,30 @@ export function renderAdDirect(doc, adId, options) { if (!adId || !doc) { fail(AD_RENDER_FAILED_REASON.MISSING_DOC_OR_ADID, `missing ${adId ? 'doc' : 'adId'}`); } else { - bid = auctionManager.findBidByAdId(adId); - - if ((doc === document && !inIframe())) { - fail(AD_RENDER_FAILED_REASON.PREVENT_WRITING_ON_MAIN_DOCUMENT, `renderAd was prevented from writing to the main document.`); - } else { - handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse: bid, doc}); - } + getBidToRender(adId).then(bidResponse => { + bid = bidResponse; + handleRender({renderFn, resizeFn, adId, options: {clickUrl: options?.clickThrough}, bidResponse, doc}); + }); } } catch (e) { fail(EXCEPTION, e.message); } } + +/** + * Insert an invisible, named iframe that can be used by creatives to locate the window Prebid is running in + * (by looking for one that has `.frames[PB_LOCATOR]` defined). + * This is necessary because in some situations creatives may be rendered inside nested iframes - Prebid is not necessarily + * in the immediate parent window. + */ +export function insertLocatorFrame() { + if (!window.frames[PB_LOCATOR]) { + if (!document.body) { + window.requestAnimationFrame(insertLocatorFrame); + } else { + const frame = createInvisibleIframe(); + frame.name = PB_LOCATOR; + document.body.appendChild(frame); + } + } +} diff --git a/src/adUnits.js b/src/adUnits.js index b0c728fd945..413fc6a7c28 100644 --- a/src/adUnits.js +++ b/src/adUnits.js @@ -1,5 +1,3 @@ -import { deepAccess } from './utils.js'; - let adUnits = {}; export function reset() { adUnits = {} @@ -54,7 +52,7 @@ export function incrementBidderWinsCounter(adunit, bidderCode) { * @returns {number} current adunit count */ export function getRequestsCounter(adunit) { - return deepAccess(adUnits, `${adunit}.requestsCounter`) || 0; + return adUnits?.[adunit]?.requestsCounter || 0; } /** @@ -64,7 +62,7 @@ export function getRequestsCounter(adunit) { * @returns {number} current adunit bidder requests count */ export function getBidderRequestsCounter(adunit, bidder) { - return deepAccess(adUnits, `${adunit}.bidders.${bidder}.requestsCounter`) || 0; + return adUnits?.[adunit]?.bidders?.[bidder]?.requestsCounter || 0; } /** @@ -74,5 +72,5 @@ export function getBidderRequestsCounter(adunit, bidder) { * @returns {number} current adunit bidder requests count */ export function getBidderWinsCounter(adunit, bidder) { - return deepAccess(adUnits, `${adunit}.bidders.${bidder}.winsCounter`) || 0; + return adUnits?.[adunit]?.bidders?.[bidder]?.winsCounter || 0; } diff --git a/src/adapterManager.js b/src/adapterManager.js index 557f4c1eee4..c39ef039af3 100644 --- a/src/adapterManager.js +++ b/src/adapterManager.js @@ -10,6 +10,7 @@ import { getUniqueIdentifierStr, getUserConfiguredParams, groupBy, + internal, isArray, isPlainObject, isValidMediaTypes, @@ -30,13 +31,15 @@ import {find, includes} from './polyfill.js'; import { getBidderRequestsCounter, getBidderWinsCounter, - getRequestsCounter, incrementBidderRequestsCounter, - incrementBidderWinsCounter, incrementRequestsCounter + getRequestsCounter, + incrementBidderRequestsCounter, + incrementBidderWinsCounter, + incrementRequestsCounter } from './adUnits.js'; import {getRefererInfo} from './refererDetection.js'; import {GDPR_GVLIDS, gdprDataHandler, gppDataHandler, uspDataHandler, } from './consentHandler.js'; import * as events from './events.js'; -import { EVENTS, S2S } from './constants.js'; +import {EVENTS, S2S} from './constants.js'; import {useMetrics} from './utils/perfMetrics.js'; import {auctionManager} from './auctionManager.js'; import {MODULE_TYPE_ANALYTICS, MODULE_TYPE_BIDDER, MODULE_TYPE_PREBID} from './activities/modules.js'; @@ -134,6 +137,7 @@ function getBids({bidderCode, auctionId, bidderRequestId, adUnits, src, metrics} bidRequestsCount: getRequestsCounter(adUnit.code), bidderRequestsCount: getBidderRequestsCounter(adUnit.code, bid.bidder), bidderWinsCount: getBidderWinsCounter(adUnit.code, bid.bidder), + deferBilling: !!adUnit.deferBilling })); return bids; }, []) @@ -230,9 +234,11 @@ export function getS2SBidderSet(s2sConfigs) { } /** - * @returns {{[PARTITIONS.CLIENT]: Array, [PARTITIONS.SERVER]: Array}} - * All the bidder codes in the given `adUnits`, divided in two arrays - - * those that should be routed to client, and server adapters (according to the configuration in `s2sConfigs`). + * @param {Array} adUnits - The ad units to be processed. + * @param {Object} s2sConfigs - The server-to-server configurations. + * @returns {Object} - An object containing arrays of bidder codes for client and server. + * @returns {Object} return.client - Array of bidder codes that should be routed to client adapters. + * @returns {Object} return.server - Array of bidder codes that should be routed to server adapters. */ export function _partitionBidders (adUnits, s2sConfigs, {getS2SBidders = getS2SBidderSet} = {}) { const serverBidders = getS2SBidders(s2sConfigs); @@ -420,7 +426,7 @@ adapterManager.callBids = (adUnits, bidRequests, addBidResponse, doneCb, request let uniqueServerRequests = serverBidderRequests.filter(serverBidRequest => serverBidRequest.uniquePbsTid === uniquePbsTid); if (s2sAdapter) { - let s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments}; + let s2sBidRequest = {'ad_units': adUnitsS2SCopy, s2sConfig, ortb2Fragments, requestBidsTimeout}; if (s2sBidRequest.ad_units.length) { let doneCbs = uniqueServerRequests.map(bidRequest => { bidRequest.start = timestamp(); @@ -551,11 +557,13 @@ adapterManager.aliasBidAdapter = function (bidderCode, alias, options) { newAdapter = new bidAdapter.constructor(); newAdapter.setBidderCode(alias); } else { + const { useBaseGvlid = false } = options || {}; let spec = bidAdapter.getSpec(); - let gvlid = options && options.gvlid; - if (spec.gvlid != null && gvlid == null) { + const gvlid = useBaseGvlid ? spec.gvlid : options?.gvlid; + if (gvlid == null && spec.gvlid != null) { logWarn(`Alias '${alias}' will NOT re-use the GVL ID of the original adapter ('${spec.code}', gvlid: ${spec.gvlid}). Functionality that requires TCF consent may not work as expected.`) } + let skipPbsAliasing = options && options.skipPbsAliasing; newAdapter = newBidder(Object.assign({}, spec, { code: alias, gvlid, skipPbsAliasing })); _aliasRegistry[alias] = bidderCode; @@ -640,7 +648,7 @@ function invokeBidderMethod(bidder, method, spec, fn, ...params) { } function tryCallBidderMethod(bidder, method, param) { - if (param?.src !== S2S.SRC) { + if (param?.source !== S2S.SRC) { const target = getBidderMethod(bidder, method); if (target != null) { invokeBidderMethod(bidder, method, ...target, param); @@ -669,9 +677,18 @@ adapterManager.callBidWonBidder = function(bidder, bid, adUnits) { tryCallBidderMethod(bidder, 'onBidWon', bid); }; -adapterManager.callBidBillableBidder = function(bid) { - tryCallBidderMethod(bid.bidder, 'onBidBillable', bid); -}; +adapterManager.triggerBilling = (() => { + const BILLED = new WeakSet(); + return (bid) => { + if (!BILLED.has(bid)) { + BILLED.add(bid); + if (bid.source === S2S.SRC && bid.burl) { + internal.triggerPixel(bid.burl); + } + tryCallBidderMethod(bid.bidder, 'onBidBillable', bid); + } + } +})(); adapterManager.callSetTargetingBidder = function(bidder, bid) { tryCallBidderMethod(bidder, 'onSetTargeting', bid); @@ -686,6 +703,10 @@ adapterManager.callBidderError = function(bidder, error, bidderRequest) { tryCallBidderMethod(bidder, 'onBidderError', param); }; +adapterManager.callAdRenderSucceededBidder = function (bidder, bid) { + tryCallBidderMethod(bidder, 'onAdRenderSucceeded', bid); +} + function resolveAlias(alias) { const seen = new Set(); while (_aliasRegistry.hasOwnProperty(alias) && !seen.has(alias)) { diff --git a/src/adapters/bidderFactory.js b/src/adapters/bidderFactory.js index 1d10c3161e5..e4829d76a1d 100644 --- a/src/adapters/bidderFactory.js +++ b/src/adapters/bidderFactory.js @@ -190,7 +190,7 @@ export function registerBidder(spec) { } } -export function guardTids(bidderCode) { +export const guardTids = memoize(({bidderCode}) => { if (isActivityAllowed(ACTIVITY_TRANSMIT_TID, activityParams(MODULE_TYPE_BIDDER, bidderCode))) { return { bidRequest: (br) => br, @@ -228,7 +228,7 @@ export function guardTids(bidderCode) { } }) } -} +}); /** * Make a new bidder from the given spec. This is exported mainly for testing. @@ -246,7 +246,7 @@ export function newBidder(spec) { if (!Array.isArray(bidderRequest.bids)) { return; } - const tidGuard = guardTids(bidderRequest.bidderCode); + const tidGuard = guardTids(bidderRequest); const adUnitCodesHandled = {}; function addBidWithCode(adUnitCode, bid) { @@ -287,7 +287,7 @@ export function newBidder(spec) { } }); - processBidderRequests(spec, validBidRequests.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest), ajax, configEnabledCallback, { + processBidderRequests(spec, validBidRequests, bidderRequest, ajax, configEnabledCallback, { onRequest: requestObject => events.emit(EVENTS.BEFORE_BIDDER_HTTP, bidderRequest, requestObject), onResponse: (resp) => { onTimelyResponse(spec.code); @@ -296,7 +296,7 @@ export function newBidder(spec) { onPaapi: (paapiConfig) => { const bidRequest = bidRequestMap[paapiConfig.bidId]; if (bidRequest) { - addComponentAuction(bidRequest, paapiConfig.config); + addPaapiConfig(bidRequest, paapiConfig); } else { logWarn('Received fledge auction configuration for an unknown bidId', paapiConfig); } @@ -323,6 +323,8 @@ export function newBidder(spec) { bid.originalCpm = bid.cpm; bid.originalCurrency = bid.currency; bid.meta = bid.meta || Object.assign({}, bid[bidRequest.bidder]); + bid.deferBilling = bidRequest.deferBilling; + bid.deferRendering = bid.deferBilling && (bid.deferRendering ?? typeof spec.onBidBillable !== 'function'); const prebidBid = Object.assign(createBid(STATUS.GOOD, bidRequest), bid, pick(bidRequest, TIDS)); addBidWithCode(bidRequest.adUnitCode, prebidBid); } else { @@ -361,17 +363,7 @@ export function newBidder(spec) { } } -// Transition from 'fledge' to 'paapi' -// TODO: remove this in prebid 9 -const PAAPI_RESPONSE_PROPS = ['paapiAuctionConfigs', 'fledgeAuctionConfigs']; -const RESPONSE_PROPS = ['bids'].concat(PAAPI_RESPONSE_PROPS); -function getPaapiConfigs(adapterResponse) { - const [paapi, fledge] = PAAPI_RESPONSE_PROPS.map(prop => adapterResponse[prop]); - if (paapi != null && fledge != null) { - throw new Error(`Adapter response should use ${PAAPI_RESPONSE_PROPS[0]} over ${PAAPI_RESPONSE_PROPS[1]}, not both`); - } - return paapi ?? fledge; -} +const RESPONSE_PROPS = ['bids', 'paapi'] /** * Run a set of bid requests - that entails converting them to HTTP requests, sending @@ -391,8 +383,8 @@ function getPaapiConfigs(adapterResponse) { export const processBidderRequests = hook('sync', function (spec, bids, bidderRequest, ajax, wrapCallback, {onRequest, onResponse, onPaapi, onError, onBid, onCompletion}) { const metrics = adapterMetrics(bidderRequest); onCompletion = metrics.startTiming('total').stopBefore(onCompletion); - - let requests = metrics.measureTime('buildRequests', () => spec.buildRequests(bids, bidderRequest)); + const tidGuard = guardTids(bidderRequest); + let requests = metrics.measureTime('buildRequests', () => spec.buildRequests(bids.map(tidGuard.bidRequest), tidGuard.bidderRequest(bidderRequest))); if (!requests || requests.length === 0) { onCompletion(); @@ -437,12 +429,12 @@ export const processBidderRequests = hook('sync', function (spec, bids, bidderRe // adapters can reply with: // a single bid // an array of bids - // a BidderAuctionResponse object ({bids: [*], paapiAuctionConfigs: [*]}) + // a BidderAuctionResponse object let bids, paapiConfigs; if (response && !Object.keys(response).some(key => !RESPONSE_PROPS.includes(key))) { bids = response.bids; - paapiConfigs = getPaapiConfigs(response); + paapiConfigs = response.paapi; } else { bids = response; } @@ -548,8 +540,8 @@ export const registerSyncInner = hook('async', function(spec, responses, gdprCon } }, 'registerSyncs') -export const addComponentAuction = hook('sync', (request, fledgeAuctionConfig) => { -}, 'addComponentAuction'); +export const addPaapiConfig = hook('sync', (request, paapiConfig) => { +}, 'addPaapiConfig'); // check that the bid has a width and height set function validBidSize(adUnitCode, bid, {index = auctionManager.index} = {}) { @@ -619,6 +611,6 @@ export function isValid(adUnitCode, bid, {index = auctionManager.index} = {}) { return true; } -function adapterMetrics(bidderRequest) { +export function adapterMetrics(bidderRequest) { return useMetrics(bidderRequest.metrics).renameWith(n => [`adapter.client.${n}`, `adapters.client.${bidderRequest.bidderCode}.${n}`]) } diff --git a/src/adloader.js b/src/adloader.js index 9c1c6b720d1..59ea66e668c 100644 --- a/src/adloader.js +++ b/src/adloader.js @@ -1,37 +1,46 @@ -import {includes} from './polyfill.js'; -import { logError, logWarn, insertElement, setScriptAttributes } from './utils.js'; +import { LOAD_EXTERNAL_SCRIPT } from './activities/activities.js'; +import { activityParams } from './activities/activityParams.js'; +import { isActivityAllowed } from './activities/rules.js'; +import { includes } from './polyfill.js'; +import { insertElement, logError, logWarn, setScriptAttributes } from './utils.js'; const _requestCache = new WeakMap(); // The below list contains modules or vendors whom Prebid allows to load external JS. const _approvedLoadExternalJSList = [ + // Prebid maintained modules: 'debugging', - 'adloox', - 'criteo', 'outstream', + // Bid Modules - only exception is on rendering edge cases, to clean up in Prebid 10: + 'improvedigital', + 'showheroes-bs', + // RTD modules: + 'aaxBlockmeter', 'adagio', - 'spotx', + 'adloox', + 'akamaidap', + 'arcspan', + 'airgrid', 'browsi', 'brandmetrics', - 'justtag', - 'tncId', - 'akamaidap', - 'ftrackId', - 'inskin', + 'clean.io', + 'humansecurity', + 'confiant', + 'contxtful', 'hadron', + 'mediafilter', 'medianet', - 'improvedigital', 'azerionedge', - 'aaxBlockmeter', - 'confiant', - 'arcspan', - 'airgrid', - 'clean.io', 'a1Media', 'geoedge', - 'mediafilter', 'qortex', 'dynamicAdBoost', - 'contxtful', + '51Degrees', + 'symitridap', + 'wurfl', + // UserId Submodules + 'justtag', + 'tncId', + 'ftrackId', 'id5', 'lucead', 'symitridap', @@ -41,12 +50,17 @@ const _approvedLoadExternalJSList = [ * Loads external javascript. Can only be used if external JS is approved by Prebid. See https://github.com/prebid/prebid-js-external-js-template#policy * Each unique URL will be loaded at most 1 time. * @param {string} url the url to load + * @param {string} moduleType moduleType of the module requesting this resource * @param {string} moduleCode bidderCode or module code of the module requesting this resource * @param {function} [callback] callback function to be called after the script is loaded * @param {Document} [doc] the context document, in which the script will be loaded, defaults to loaded document * @param {object} attributes an object of attributes to be added to the script with setAttribute by [key] and [value]; Only the attributes passed in the first request of a url will be added. */ -export function loadExternalScript(url, moduleCode, callback, doc, attributes) { +export function loadExternalScript(url, moduleType, moduleCode, callback, doc, attributes) { + if (!isActivityAllowed(LOAD_EXTERNAL_SCRIPT, activityParams(moduleType, moduleCode))) { + return; + } + if (!moduleCode || !url) { logError('cannot load external script without url and moduleCode'); return; diff --git a/src/ajax.js b/src/ajax.js index ef4c2e4bcb4..7f9857ad18d 100644 --- a/src/ajax.js +++ b/src/ajax.js @@ -52,6 +52,9 @@ export function toFetchRequest(url, data, options = {}) { // but we're not in a secure context rqOpts.browsingTopics = true; } + if (options.keepalive) { + rqOpts.keepalive = true; + } return dep.makeRequest(url, rqOpts); } @@ -101,6 +104,7 @@ function toXHR({status, statusText = '', headers, url}, responseText) { return xml; } return { + // eslint-disable-next-line prebid/no-global readyState: XMLHttpRequest.DONE, status, statusText, @@ -127,7 +131,7 @@ export function attachCallbacks(fetchPm, callback) { success: typeof callback === 'function' ? callback : () => null, error: (e, x) => logError('Network error', e, x) }; - fetchPm.then(response => response.text().then((responseText) => [response, responseText])) + return fetchPm.then(response => response.text().then((responseText) => [response, responseText])) .then(([response, responseText]) => { const xhr = toXHR(response, responseText); response.ok || response.status === 304 ? success(responseText, xhr) : error(response.statusText, xhr); @@ -144,5 +148,19 @@ export function ajaxBuilder(timeout = 3000, {request, done} = {}) { }; } +/** + * simple wrapper around sendBeacon such that invocations of navigator.sendBeacon can be centrally maintained. + * verifies that the navigator and sendBeacon are defined for maximum compatibility + * @param {string} url The URL that will receive the data. Can be relative or absolute. + * @param {*} data An ArrayBuffer, a TypedArray, a DataView, a Blob, a string literal or object, a FormData or a URLSearchParams object containing the data to send. + * @returns {boolean} true if the user agent successfully queued the data for transfer. Otherwise, it returns false. + */ +export function sendBeacon(url, data) { + if (!window.navigator || !window.navigator.sendBeacon) { + return false; + } + return window.navigator.sendBeacon(url, data); +} + export const ajax = ajaxBuilder(); export const fetch = fetcherFactory(); diff --git a/src/auction.js b/src/auction.js index 26845936797..759397275d5 100644 --- a/src/auction.js +++ b/src/auction.js @@ -66,7 +66,6 @@ */ import { - callBurl, deepAccess, generateUUID, getValue, @@ -82,7 +81,7 @@ import { } from './utils.js'; import {getPriceBucketString} from './cpmBucketManager.js'; import {getNativeTargeting, isNativeResponse, setNativeResponseProperties} from './native.js'; -import {getCacheUrl, store} from './videoCache.js'; +import {batchAndStore} from './videoCache.js'; import {Renderer} from './Renderer.js'; import {config} from './config.js'; import {userSync} from './userSync.js'; @@ -94,7 +93,7 @@ import {auctionManager} from './auctionManager.js'; import {bidderSettings} from './bidderSettings.js'; import * as events from './events.js'; import adapterManager from './adapterManager.js'; -import { EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, S2S, TARGETING_KEYS } from './constants.js'; +import {EVENTS, GRANULARITY_OPTIONS, JSON_MAPPING, REJECTION_REASON, S2S, TARGETING_KEYS} from './constants.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {useMetrics} from './utils/perfMetrics.js'; import {adjustCpm} from './utils/cpm.js'; @@ -150,6 +149,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a const _timeout = cbTimeout; const _timelyRequests = new Set(); const done = defer(); + const requestsDone = defer(); let _bidsRejected = []; let _callback = callback; let _bidderRequests = []; @@ -320,6 +320,7 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } } }, _timeout, onTimelyResponse, ortb2Fragments); + requestsDone.resolve(); } }; @@ -370,11 +371,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a } function addWinningBid(winningBid) { - const winningAd = adUnits.find(adUnit => adUnit.adUnitId === winningBid.adUnitId); _winningBids = _winningBids.concat(winningBid); - callBurl(winningBid); adapterManager.callBidWonBidder(winningBid.adapterCode || winningBid.bidder, winningBid, adUnits); - if (winningAd && !winningAd.deferBilling) adapterManager.callBidBillableBidder(winningBid); + if (!winningBid.deferBilling) { + adapterManager.triggerBilling(winningBid) + } } function setBidTargeting(bid) { @@ -408,7 +409,8 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a getNonBids: () => _nonBids, getFPD: () => ortb2Fragments, getMetrics: () => metrics, - end: done.promise + end: done.promise, + requestsDone: requestsDone.promise }; } @@ -421,7 +423,11 @@ export function newAuction({adUnits, adUnitCodes, callback, cbTimeout, labels, a * @param {function(String): void} reject a function that, when called, rejects `bid` with the given reason. */ export const addBidResponse = hook('sync', function(adUnitCode, bid, reject) { - this.dispatch.call(null, adUnitCode, bid); + if (!isValidPrice(bid)) { + reject(REJECTION_REASON.PRICE_TOO_HIGH) + } else { + this.dispatch.call(null, adUnitCode, bid); + } }, 'addBidResponse'); /** @@ -576,68 +582,10 @@ function tryAddVideoBid(auctionInstance, bidResponse, afterBidAdded, {index = au } } -const _storeInCache = (batch) => { - store(batch.map(entry => entry.bidResponse), function (error, cacheIds) { - cacheIds.forEach((cacheId, i) => { - const { auctionInstance, bidResponse, afterBidAdded } = batch[i]; - if (error) { - logWarn(`Failed to save to the video cache: ${error}. Video bid must be discarded.`); - } else { - if (cacheId.uuid === '') { - logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); - } else { - bidResponse.videoCacheKey = cacheId.uuid; - if (!bidResponse.vastUrl) { - bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); - } - addBidToAuction(auctionInstance, bidResponse); - afterBidAdded(); - } - } - }); - }); -}; - -const storeInCache = FEATURES.VIDEO ? _storeInCache : () => {}; - -let batchSize, batchTimeout; -config.getConfig('cache', (cacheConfig) => { - batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 - ? cacheConfig.cache.batchSize - : 1; - batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 - ? cacheConfig.cache.batchTimeout - : 0; -}); - -export const batchingCache = (timeout = setTimeout, cache = storeInCache) => { - let batches = [[]]; - let debouncing = false; - const noTimeout = cb => cb(); - - return function(auctionInstance, bidResponse, afterBidAdded) { - const batchFunc = batchTimeout > 0 ? timeout : noTimeout; - if (batches[batches.length - 1].length >= batchSize) { - batches.push([]); - } - - batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); - - if (!debouncing) { - debouncing = true; - batchFunc(() => { - batches.forEach(cache); - batches = [[]]; - debouncing = false; - }, batchTimeout); - } - } -}; - -const batchAndStore = batchingCache(); - export const callPrebidCache = hook('async', function(auctionInstance, bidResponse, afterBidAdded, videoMediaType) { - batchAndStore(auctionInstance, bidResponse, afterBidAdded); + if (FEATURES.VIDEO) { + batchAndStore(auctionInstance, bidResponse, afterBidAdded); + } }, 'callPrebidCache'); /** @@ -687,15 +635,15 @@ function getPreparedBidForAuction(bid, {index = auctionManager.index} = {}) { var renderer = null; // the renderer for the mediaType takes precendence - if (mediaTypeRenderer && mediaTypeRenderer.url && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { + if (mediaTypeRenderer && mediaTypeRenderer.render && !(mediaTypeRenderer.backupOnly === true && bid.renderer)) { renderer = mediaTypeRenderer; - } else if (bidRenderer && bidRenderer.url && bidRenderer.render && !(bidRenderer.backupOnly === true && bid.renderer)) { + } else if (bidRenderer && bidRenderer.render && !(bidRenderer.backupOnly === true && bid.renderer)) { renderer = bidRenderer; } if (renderer) { // be aware, an adapter could already have installed the bidder, in which case this overwrite's the existing adapter - bid.renderer = Renderer.install({ url: renderer.url, config: renderer.options });// rename options to config, to make it consistent? + bid.renderer = Renderer.install({ url: renderer.url, config: renderer.options, renderNow: renderer.url == null });// rename options to config, to make it consistent? bid.renderer.setRender(renderer.render); } @@ -974,3 +922,17 @@ function groupByPlacement(bidsByPlacement, bid) { bidsByPlacement[bid.adUnitCode].bids.push(bid); return bidsByPlacement; } + +/** + * isValidPrice is price validation function + * which checks if price from bid response + * is not higher than top limit set in config + * @type {Function} + * @param bid + * @returns {boolean} + */ +function isValidPrice(bid) { + const maxBidValue = config.getConfig('maxBid'); + if (!maxBidValue || !bid.cpm) return true; + return maxBidValue >= Number(bid.cpm); +} diff --git a/src/auctionIndex.js b/src/auctionIndex.js index afae2089518..d0b8355352a 100644 --- a/src/auctionIndex.js +++ b/src/auctionIndex.js @@ -65,6 +65,9 @@ export function AuctionIndex(getAuctions) { .flatMap(ber => ber.bids) .find(br => br && br.bidId === requestId); } + }, + getOrtb2(bid) { + return this.getBidderRequest(bid)?.ortb2 || this.getAuction(bid)?.getFPD()?.global?.ortb2 } }); } diff --git a/src/auctionManager.js b/src/auctionManager.js index a1ab1a859da..27a2c2beaf2 100644 --- a/src/auctionManager.js +++ b/src/auctionManager.js @@ -73,11 +73,10 @@ export function newAuctionManager() { auctionManager.addWinningBid = function(bid) { const metrics = useMetrics(bid.metrics); metrics.checkpoint('bidWon'); - metrics.timeBetween('auctionEnd', 'bidWon', 'render.pending'); - metrics.timeBetween('requestBids', 'bidWon', 'render.e2e'); + metrics.timeBetween('auctionEnd', 'bidWon', 'adserver.pending'); + metrics.timeBetween('requestBids', 'bidWon', 'adserver.e2e'); const auction = getAuction(bid.auctionId); if (auction) { - bid.status = BID_STATUS.RENDERED; auction.addWinningBid(bid); } else { logWarn(`Auction not found when adding winning bid`); diff --git a/src/config.js b/src/config.js index f4dd0de9612..88affc40faf 100644 --- a/src/config.js +++ b/src/config.js @@ -36,8 +36,9 @@ const DEFAULT_DISABLE_AJAX_TIMEOUT = false; const DEFAULT_BID_CACHE = false; const DEFAULT_DEVICE_ACCESS = true; const DEFAULT_MAX_NESTED_IFRAMES = 10; +const DEFAULT_MAXBID_VALUE = 5000 -const DEFAULT_TIMEOUTBUFFER = 400; +const DEFAULT_IFRAMES_CONFIG = {}; export const RANDOM = 'random'; const FIXED = 'fixed'; @@ -59,89 +60,159 @@ const GRANULARITY_OPTIONS = { const ALL_TOPICS = '*'; -export function newConfig() { - let listeners = []; - let defaults; - let config; - let bidderConfig; - let currBidder = null; - - function resetConfig() { - defaults = {}; - - function getProp(name) { - return props[name].val; - } +function attachProperties(config, useDefaultValues = true) { + const values = useDefaultValues ? { + priceGranularity: GRANULARITY_OPTIONS.MEDIUM, + customPriceBucket: {}, + mediaTypePriceGranularity: {}, + bidderSequence: DEFAULT_BIDDER_SEQUENCE, + auctionOptions: {} + } : {} + + function getProp(name) { + return values[name]; + } - function setProp(name, val) { - props[name].val = val; + function setProp(name, val) { + if (!values.hasOwnProperty(name)) { + Object.defineProperty(config, name, {enumerable: true}); } + values[name] = val; + } - const props = { - publisherDomain: { - set(val) { - if (val != null) { - logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') + const props = { + publisherDomain: { + set(val) { + if (val != null) { + logWarn('publisherDomain is deprecated and has no effect since v7 - use pageUrl instead') + } + setProp('publisherDomain', val); + } + }, + priceGranularity: { + set(val) { + if (validatePriceGranularity(val)) { + if (typeof val === 'string') { + setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); + } else if (isPlainObject(val)) { + setProp('customPriceBucket', val); + setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) + logMessage('Using custom price granularity'); } - setProp('publisherDomain', val); } - }, - priceGranularity: { - val: GRANULARITY_OPTIONS.MEDIUM, - set(val) { - if (validatePriceGranularity(val)) { + } + }, + customPriceBucket: {}, + mediaTypePriceGranularity: { + set(val) { + val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { + if (validatePriceGranularity(val[item])) { if (typeof val === 'string') { - setProp('priceGranularity', (hasGranularity(val)) ? val : GRANULARITY_OPTIONS.MEDIUM); + aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); } else if (isPlainObject(val)) { - setProp('customPriceBucket', val); - setProp('priceGranularity', GRANULARITY_OPTIONS.CUSTOM) - logMessage('Using custom price granularity'); + aggregate[item] = val[item]; + logMessage(`Using custom price granularity for ${item}`); } + } else { + logWarn(`Invalid price granularity for media type: ${item}`); } + return aggregate; + }, {})); + } + }, + bidderSequence: { + set(val) { + if (VALID_ORDERS[val]) { + setProp('bidderSequence', val); + } else { + logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); } - }, - customPriceBucket: { - val: {}, - set() {} - }, - mediaTypePriceGranularity: { - val: {}, - set(val) { - val != null && setProp('mediaTypePriceGranularity', Object.keys(val).reduce((aggregate, item) => { - if (validatePriceGranularity(val[item])) { - if (typeof val === 'string') { - aggregate[item] = (hasGranularity(val[item])) ? val[item] : getProp('priceGranularity'); - } else if (isPlainObject(val)) { - aggregate[item] = val[item]; - logMessage(`Using custom price granularity for ${item}`); - } - } else { - logWarn(`Invalid price granularity for media type: ${item}`); - } - return aggregate; - }, {})); + } + }, + auctionOptions: { + set(val) { + if (validateauctionOptions(val)) { + setProp('auctionOptions', val); } - }, - bidderSequence: { - val: DEFAULT_BIDDER_SEQUENCE, - set(val) { - if (VALID_ORDERS[val]) { - setProp('bidderSequence', val); - } else { - logWarn(`Invalid order: ${val}. Bidder Sequence was not set.`); - } + } + } + } + + Object.defineProperties(config, Object.fromEntries( + Object.entries(props) + .map(([k, def]) => [k, Object.assign({ + get: getProp.bind(null, k), + set: setProp.bind(null, k), + enumerable: values.hasOwnProperty(k), + configurable: !values.hasOwnProperty(k) + }, def)]) + )); + + return config; + + function hasGranularity(val) { + return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); + } + + function validatePriceGranularity(val) { + if (!val) { + logError('Prebid Error: no value passed to `setPriceGranularity()`'); + return false; + } + if (typeof val === 'string') { + if (!hasGranularity(val)) { + logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); + } + } else if (isPlainObject(val)) { + if (!isValidPriceConfig(val)) { + logError('Invalid custom price value passed to `setPriceGranularity()`'); + return false; + } + } + return true; + } + + function validateauctionOptions(val) { + if (!isPlainObject(val)) { + logWarn('Auction Options must be an object') + return false + } + + for (let k of Object.keys(val)) { + if (k !== 'secondaryBidders' && k !== 'suppressStaleRender' && k !== 'suppressExpiredRender') { + logWarn(`Auction Options given an incorrect param: ${k}`) + return false + } + if (k === 'secondaryBidders') { + if (!isArray(val[k])) { + logWarn(`Auction Options ${k} must be of type Array`); + return false + } else if (!val[k].every(isStr)) { + logWarn(`Auction Options ${k} must be only string`); + return false } - }, - auctionOptions: { - val: {}, - set(val) { - if (validateauctionOptions(val)) { - setProp('auctionOptions', val); - } + } else if (k === 'suppressStaleRender' || k === 'suppressExpiredRender') { + if (!isBoolean(val[k])) { + logWarn(`Auction Options ${k} must be of type boolean`); + return false; } } } - let newConfig = { + return true; + } +} + +export function newConfig() { + let listeners = []; + let defaults; + let config; + let bidderConfig; + let currBidder = null; + + function resetConfig() { + defaults = {}; + + let newConfig = attachProperties({ // `debug` is equivalent to legacy `pbjs.logging` property debug: DEFAULT_DEBUG, bidderTimeout: DEFAULT_BIDDER_TIMEOUT, @@ -154,22 +225,17 @@ export function newConfig() { */ deviceAccess: DEFAULT_DEVICE_ACCESS, - // timeout buffer to adjust for bidder CDN latency - timeoutBuffer: DEFAULT_TIMEOUTBUFFER, disableAjaxTimeout: DEFAULT_DISABLE_AJAX_TIMEOUT, // default max nested iframes for referer detection maxNestedIframes: DEFAULT_MAX_NESTED_IFRAMES, - }; - Object.defineProperties(newConfig, - Object.fromEntries(Object.entries(props) - .map(([k, def]) => [k, Object.assign({ - get: getProp.bind(null, k), - set: setProp.bind(null, k), - enumerable: true, - }, def)])) - ); + // default max bid + maxBid: DEFAULT_MAXBID_VALUE, + userSync: { + topics: DEFAULT_IFRAMES_CONFIG + } + }); if (config) { callSubscribers( @@ -185,57 +251,6 @@ export function newConfig() { config = newConfig; bidderConfig = {}; - - function hasGranularity(val) { - return find(Object.keys(GRANULARITY_OPTIONS), option => val === GRANULARITY_OPTIONS[option]); - } - - function validatePriceGranularity(val) { - if (!val) { - logError('Prebid Error: no value passed to `setPriceGranularity()`'); - return false; - } - if (typeof val === 'string') { - if (!hasGranularity(val)) { - logWarn('Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.'); - } - } else if (isPlainObject(val)) { - if (!isValidPriceConfig(val)) { - logError('Invalid custom price value passed to `setPriceGranularity()`'); - return false; - } - } - return true; - } - - function validateauctionOptions(val) { - if (!isPlainObject(val)) { - logWarn('Auction Options must be an object') - return false - } - - for (let k of Object.keys(val)) { - if (k !== 'secondaryBidders' && k !== 'suppressStaleRender') { - logWarn(`Auction Options given an incorrect param: ${k}`) - return false - } - if (k === 'secondaryBidders') { - if (!isArray(val[k])) { - logWarn(`Auction Options ${k} must be of type Array`); - return false - } else if (!val[k].every(isStr)) { - logWarn(`Auction Options ${k} must be only string`); - return false - } - } else if (k === 'suppressStaleRender') { - if (!isBoolean(val[k])) { - logWarn(`Auction Options ${k} must be of type boolean`); - return false; - } - } - } - return true; - } } /** @@ -446,14 +461,14 @@ export function newConfig() { check(config); config.bidders.forEach(bidder => { if (!bidderConfig[bidder]) { - bidderConfig[bidder] = {}; + bidderConfig[bidder] = attachProperties({}, false); } Object.keys(config.config).forEach(topic => { let option = config.config[topic]; - - if (isPlainObject(option)) { + const currentConfig = bidderConfig[bidder][topic]; + if (isPlainObject(option) && (currentConfig == null || isPlainObject(currentConfig))) { const func = mergeFlag ? mergeDeep : Object.assign; - bidderConfig[bidder][topic] = func({}, bidderConfig[bidder][topic] || {}, option); + bidderConfig[bidder][topic] = func({}, currentConfig || {}, option); } else { bidderConfig[bidder][topic] = option; } diff --git a/src/consentHandler.js b/src/consentHandler.js index 5b5d8b805cd..87d1e1a6e23 100644 --- a/src/consentHandler.js +++ b/src/consentHandler.js @@ -10,14 +10,6 @@ import {config} from './config.js'; */ export const VENDORLESS_GVLID = Object.freeze({}); -/** - * Placeholder gvlid for when device.ext.cdep is present (Privacy Sandbox cookie deprecation label). When this value is used as gvlid, the gdpr - * enforcement module will look to see that publisher consent was given. - * - * see https://github.com/prebid/Prebid.js/issues/10516 - */ -export const FIRST_PARTY_GVLID = Object.freeze({}); - export class ConsentHandler { #enabled; #data; @@ -108,7 +100,6 @@ class UspConsentHandler extends ConsentHandler { const consentData = this.getConsentData(); if (consentData && this.generatedTime) { return { - usp: consentData, generatedAt: this.generatedTime }; } @@ -163,14 +154,19 @@ export function gvlidRegistry() { } } }, + /** + * @typedef {Object} GvlIdResult + * @property {Object.} modules - A map from module type to that module's GVL ID. + * @property {number} [gvlid] - The single GVL ID for this family of modules (only defined if all modules with this name declared the same ID). + */ + /** * Get a module's GVL ID(s). * - * @param {string} moduleName - * @return {{modules: {[moduleType]: number}, gvlid?: number}} an object where: + * @param {string} moduleName - The name of the module. + * @return {GvlIdResult} An object where: * `modules` is a map from module type to that module's GVL ID; - * `gvlid` is the single GVL ID for this family of modules (only defined - * if all modules with this name declared the same ID). + * `gvlid` is the single GVL ID for this family of modules (only defined if all modules with this name declare the same ID). */ get(moduleName) { const result = {modules: registry[moduleName] || {}}; diff --git a/src/constants.js b/src/constants.js index b40b7ddb9b0..1a17cf3eec0 100644 --- a/src/constants.js +++ b/src/constants.js @@ -40,8 +40,14 @@ export const EVENTS = { AUCTION_DEBUG: 'auctionDebug', BID_VIEWABLE: 'bidViewable', STALE_RENDER: 'staleRender', + EXPIRED_RENDER: 'expiredRender', BILLABLE_EVENT: 'billableEvent', - BID_ACCEPTED: 'bidAccepted' + BID_ACCEPTED: 'bidAccepted', + RUN_PAAPI_AUCTION: 'paapiRunAuction', + PBS_ANALYTICS: 'pbsAnalytics', + PAAPI_BID: 'paapiBid', + PAAPI_NO_BID: 'paapiNoBid', + PAAPI_ERROR: 'paapiError', }; export const AD_RENDER_FAILED_REASON = { @@ -135,7 +141,8 @@ export const REJECTION_REASON = { FLOOR_NOT_MET: 'Bid does not meet price floor', CANNOT_CONVERT_CURRENCY: 'Unable to convert currency', DSA_REQUIRED: 'Bid does not provide required DSA transparency info', - DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method' + DSA_MISMATCH: 'Bid indicates inappropriate DSA rendering method', + PRICE_TOO_HIGH: 'Bid price exceeds maximum value' }; export const PREBID_NATIVE_DATA_KEYS_TO_ORTB = { @@ -188,3 +195,5 @@ export const MESSAGES = { NATIVE: 'Prebid Native', EVENT: 'Prebid Event' }; + +export const PB_LOCATOR = '__pb_locator__'; diff --git a/src/debugging.js b/src/debugging.js index f5d13d1a134..69c129da9a2 100644 --- a/src/debugging.js +++ b/src/debugging.js @@ -5,6 +5,7 @@ import {logMessage, prefixLog} from './utils.js'; import {createBid} from './bidfactory.js'; import {loadExternalScript} from './adloader.js'; import {GreedyPromise} from './utils/promise.js'; +import { MODULE_TYPE_PREBID } from './activities/modules.js'; export const DEBUG_KEY = '__$$PREBID_GLOBAL$$_debugging__'; @@ -14,7 +15,7 @@ function isDebuggingInstalled() { function loadScript(url) { return new GreedyPromise((resolve) => { - loadExternalScript(url, 'debugging', resolve); + loadExternalScript(url, MODULE_TYPE_PREBID, 'debugging', resolve); }); } @@ -73,6 +74,7 @@ export const reset = ctl.reset; export function loadSession() { let storage = null; try { + // eslint-disable-next-line prebid/no-global storage = window.sessionStorage; } catch (e) {} diff --git a/src/fpd/enrichment.js b/src/fpd/enrichment.js index 49e2f7d7cad..ec5047238a2 100644 --- a/src/fpd/enrichment.js +++ b/src/fpd/enrichment.js @@ -23,9 +23,9 @@ export const dep = { const oneClient = clientSectionChecker('FPD') /** - * Enrich an ortb2 object with first party data. - * @param {Promise[{}]} fpd: a promise to an ortb2 object. - * @returns: {Promise[{}]}: a promise to an enriched ortb2 object. + * Enrich an ortb2 object with first-party data. + * @param {Promise} fpd - A promise that resolves to an ortb2 object. + * @returns {Promise} - A promise that resolves to an enriched ortb2 object. */ export const enrichFPD = hook('sync', (fpd) => { const promArr = [fpd, getSUA().catch(() => null), tryToGetCdepLabel().catch(() => null)]; @@ -33,7 +33,6 @@ export const enrichFPD = hook('sync', (fpd) => { return GreedyPromise.all(promArr) .then(([ortb2, sua, cdep]) => { const ri = dep.getRefererInfo(); - mergeLegacySetConfigs(ortb2); Object.entries(ENRICHMENTS).forEach(([section, getEnrichments]) => { const data = getEnrichments(ortb2, ri); if (data && Object.keys(data).length > 0) { @@ -64,17 +63,6 @@ export const enrichFPD = hook('sync', (fpd) => { }); }); -function mergeLegacySetConfigs(ortb2) { - // merge in values from "legacy" setConfig({app, site, device}) - // TODO: deprecate these eventually - ['app', 'site', 'device'].forEach(prop => { - const cfg = config.getConfig(prop); - if (cfg != null) { - ortb2[prop] = mergeDeep({}, cfg, ortb2[prop]); - } - }) -} - function winFallback(fn) { try { return fn(dep.getWindowTop()); @@ -111,8 +99,13 @@ const ENRICHMENTS = { }, device() { return winFallback((win) => { - const w = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; - const h = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; + // screen.width and screen.height are the physical dimensions of the screen + const w = win.screen.width; + const h = win.screen.height; + + // vpw and vph are the viewport dimensions of the browser window + const vpw = win.innerWidth || win.document.documentElement.clientWidth || win.document.body.clientWidth; + const vph = win.innerHeight || win.document.documentElement.clientHeight || win.document.body.clientHeight; const device = { w, @@ -120,6 +113,10 @@ const ENRICHMENTS = { dnt: getDNT() ? 1 : 0, ua: win.navigator.userAgent, language: win.navigator.language.split('-').shift(), + ext: { + vpw, + vph, + }, }; if (win.navigator?.webdriver) { @@ -132,7 +129,7 @@ const ENRICHMENTS = { regs() { const regs = {}; if (winFallback((win) => win.navigator.globalPrivacyControl)) { - deepSetValue(regs, 'ext.gpc', 1); + deepSetValue(regs, 'ext.gpc', '1'); } const coppa = config.getConfig('coppa'); if (typeof coppa === 'boolean') { diff --git a/src/native.js b/src/native.js index f001200000d..a641beb71d5 100644 --- a/src/native.js +++ b/src/native.js @@ -15,6 +15,8 @@ import {includes} from './polyfill.js'; import {auctionManager} from './auctionManager.js'; import {NATIVE_ASSET_TYPES, NATIVE_IMAGE_TYPES, PREBID_NATIVE_DATA_KEYS_TO_ORTB, NATIVE_KEYS_THAT_ARE_NOT_ASSETS, NATIVE_KEYS} from './constants.js'; import {NATIVE} from './mediaTypes.js'; +import {getRenderingData} from './adRendering.js'; +import {getCreativeRendererSource} from './creativeRenderers.js'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -362,7 +364,7 @@ export function getNativeTargeting(bid, {index = auctionManager.index} = {}) { let keyValues = {}; const adUnit = index.getAdUnit(bid); - const globalSendTargetingKeys = deepAccess( + const globalSendTargetingKeys = adUnit?.nativeParams?.ortb == null && deepAccess( adUnit, `nativeParams.sendTargetingKeys` ) !== false; @@ -434,11 +436,24 @@ export function getNativeRenderingData(bid, adUnit, keys) { } function assetsMessage(data, adObject, keys, {index = auctionManager.index} = {}) { - return { + const msg = { message: 'assetResponse', adId: data.adId, - ...getNativeRenderingData(adObject, index.getAdUnit(adObject), keys) }; + let renderData = getRenderingData(adObject).native; + if (renderData) { + // if we have native rendering data (set up by the nativeRendering module) + // include it in full ("all assets") together with the renderer. + // this is to allow PUC to use dynamic renderers without requiring changes in creative setup + msg.native = Object.assign({}, renderData); + msg.renderer = getCreativeRendererSource(adObject); + if (keys != null) { + renderData.assets = renderData.assets.filter(({key}) => keys.includes(key)) + } + } else { + renderData = getNativeRenderingData(adObject, index.getAdUnit(adObject), keys); + } + return Object.assign(msg, renderData); } const NATIVE_KEYS_INVERTED = Object.fromEntries(Object.entries(NATIVE_KEYS).map(([k, v]) => [v, k])); @@ -791,20 +806,20 @@ export function toOrtbNativeResponse(legacyResponse, ortbRequest) { export function toLegacyResponse(ortbResponse, ortbRequest) { const legacyResponse = {}; const requestAssets = ortbRequest?.assets || []; - legacyResponse.clickUrl = ortbResponse.link.url; + legacyResponse.clickUrl = ortbResponse.link?.url; legacyResponse.privacyLink = ortbResponse.privacy; for (const asset of ortbResponse?.assets || []) { const requestAsset = requestAssets.find(reqAsset => asset.id === reqAsset.id); if (asset.title) { legacyResponse.title = asset.title.text; } else if (asset.img) { - legacyResponse[requestAsset.img.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { + legacyResponse[requestAsset?.img?.type === NATIVE_IMAGE_TYPES.MAIN ? 'image' : 'icon'] = { url: asset.img.url, width: asset.img.w, height: asset.img.h }; } else if (asset.data) { - legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset.data.type]]] = asset.data.value; + legacyResponse[PREBID_NATIVE_DATA_KEYS_TO_ORTB_INVERSE[NATIVE_ASSET_TYPES_INVERSE[requestAsset?.data?.type]]] = asset.data.value; } } diff --git a/src/prebid.js b/src/prebid.js index 7f2d8798e2a..2a9b88cd47d 100644 --- a/src/prebid.js +++ b/src/prebid.js @@ -39,9 +39,15 @@ import {newMetrics, useMetrics} from './utils/perfMetrics.js'; import {defer, GreedyPromise} from './utils/promise.js'; import {enrichFPD} from './fpd/enrichment.js'; import {allConsent} from './consentHandler.js'; -import {renderAdDirect} from './adRendering.js'; +import { + insertLocatorFrame, + markBidAsRendered, + markWinningBid, + renderAdDirect, + renderIfDeferred +} from './adRendering.js'; import {getHighestCpm} from './utils/reducers.js'; -import {fillVideoDefaults} from './video.js'; +import {fillVideoDefaults, validateOrtbVideoFields} from './video.js'; const pbjsInstance = getGlobal(); const { triggerUserSyncs } = userSync; @@ -100,6 +106,22 @@ function validateSizes(sizes, targLength) { return cleanSizes; } +export function setBattrForAdUnit(adUnit, mediaType) { + const ortb2Imp = adUnit.ortb2Imp || {}; + const mediaTypes = adUnit.mediaTypes || {}; + + if (ortb2Imp[mediaType]?.battr && mediaTypes[mediaType]?.battr && (ortb2Imp[mediaType]?.battr !== mediaTypes[mediaType]?.battr)) { + logWarn(`Ad unit ${adUnit.code} specifies conflicting ortb2Imp.${mediaType}.battr and mediaTypes.${mediaType}.battr, the latter will be ignored`, adUnit); + } + + const battr = ortb2Imp[mediaType]?.battr || mediaTypes[mediaType]?.battr; + + if (battr != null) { + deepSetValue(adUnit, `ortb2Imp.${mediaType}.battr`, battr); + deepSetValue(adUnit, `mediaTypes.${mediaType}.battr`, battr); + } +} + function validateBannerMediaType(adUnit) { const validatedAdUnit = deepClone(adUnit); const banner = validatedAdUnit.mediaTypes.banner; @@ -112,6 +134,7 @@ function validateBannerMediaType(adUnit) { logError('Detected a mediaTypes.banner object without a proper sizes field. Please ensure the sizes are listed like: [[300, 250], ...]. Removing invalid mediaTypes.banner object from request.'); delete validatedAdUnit.mediaTypes.banner } + setBattrForAdUnit(validatedAdUnit, 'banner'); return validatedAdUnit; } @@ -134,14 +157,35 @@ function validateVideoMediaType(adUnit) { delete validatedAdUnit.mediaTypes.video.playerSize; } } + validateOrtbVideoFields(validatedAdUnit); + setBattrForAdUnit(validatedAdUnit, 'video'); return validatedAdUnit; } function validateNativeMediaType(adUnit) { + function err(msg) { + logError(`Error in adUnit "${adUnit.code}": ${msg}. Removing native request from ad unit`, adUnit); + delete validatedAdUnit.mediaTypes.native; + return validatedAdUnit; + } + function checkDeprecated(onDeprecated) { + for (const key of ['sendTargetingKeys', 'types']) { + if (native.hasOwnProperty(key)) { + const res = onDeprecated(key); + if (res) return res; + } + } + } const validatedAdUnit = deepClone(adUnit); const native = validatedAdUnit.mediaTypes.native; // if native assets are specified in OpenRTB format, remove legacy assets and print a warn. if (native.ortb) { + if (native.ortb.assets?.some(asset => !isNumber(asset.id) || asset.id < 0 || asset.id % 1 !== 0)) { + return err('native asset ID must be a nonnegative integer'); + } + if (checkDeprecated(key => err(`ORTB native requests cannot specify "${key}"`))) { + return validatedAdUnit; + } const legacyNativeKeys = Object.keys(NATIVE_KEYS).filter(key => NATIVE_KEYS[key].includes('hb_native_')); const nativeKeys = Object.keys(native); const intersection = nativeKeys.filter(nativeKey => legacyNativeKeys.includes(nativeKey)); @@ -149,6 +193,8 @@ function validateNativeMediaType(adUnit) { logError(`when using native OpenRTB format, you cannot use legacy native properties. Deleting ${intersection} keys from request.`); intersection.forEach(legacyKey => delete validatedAdUnit.mediaTypes.native[legacyKey]); } + } else { + checkDeprecated(key => `mediaTypes.native.${key} is deprecated, consider using native ORTB instead`, adUnit); } if (native.image && native.image.sizes && !Array.isArray(native.image.sizes)) { logError('Please use an array of sizes for native.image.sizes field. Removing invalid mediaTypes.native.image.sizes property from request.'); @@ -162,6 +208,7 @@ function validateNativeMediaType(adUnit) { logError('Please use an array of sizes for native.icon.sizes field. Removing invalid mediaTypes.native.icon.sizes property from request.'); delete validatedAdUnit.mediaTypes.native.icon.sizes; } + setBattrForAdUnit(validatedAdUnit, 'native'); return validatedAdUnit; } @@ -172,7 +219,6 @@ function validateAdUnitPos(adUnit, mediaType) { let warning = `Value of property 'pos' on ad unit ${adUnit.code} should be of type: Number`; logWarn(warning); - events.emit(EVENTS.AUCTION_DEBUG, { type: 'WARNING', arguments: warning }); delete adUnit.mediaTypes[mediaType].pos; } @@ -283,7 +329,7 @@ pbjsInstance.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 + * @param adunitCode {string} adUnitCode to get the bid responses for * @alias module:pbjs.getHighestUnusedBidResponseForAdUnitCode * @returns {Object} returnObj return bid */ @@ -393,7 +439,7 @@ pbjsInstance.getBidResponsesForAdUnitCode = function (adUnitCode) { /** * Set query string targeting on one or more GPT ad units. * @param {(string|string[])} adUnit a single `adUnit.code` or multiple. - * @param {function(object)} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; + * @param {function(object): function(string): boolean} customSlotMatching gets a GoogleTag slot and returns a filter function for adUnitCode, so you can decide to match on either eg. return slot => { return adUnitCode => { return slot.getSlotElementId() === 'myFavoriteDivId'; } }; * @alias module:pbjs.setTargetingForGPTAsync */ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { @@ -402,31 +448,12 @@ pbjsInstance.setTargetingForGPTAsync = function (adUnit, customSlotMatching) { logError('window.googletag is not defined on the page'); return; } - - // get our ad unit codes - let targetingSet = targeting.getAllTargeting(adUnit); - - // first reset any old targeting - targeting.resetPresetTargeting(adUnit, customSlotMatching); - - // now set new targeting keys - targeting.setTargetingForGPT(targetingSet, customSlotMatching); - - Object.keys(targetingSet).forEach((adUnitCode) => { - Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => { - if (targetingKey === 'hb_adid') { - auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET); - } - }); - }); - - // emit event - events.emit(SET_TARGETING, targetingSet); + targeting.setTargetingForGPT(adUnit, customSlotMatching); }; /** * Set query string targeting on all AST (AppNexus Seller Tag) ad units. Note that this function has to be called after all ad units on page are defined. For working example code, see [Using Prebid.js with AppNexus Publisher Ad Server](http://prebid.org/dev-docs/examples/use-prebid-with-appnexus-ad-server.html). - * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes + * @param {(string|string[])} adUnitCodes adUnitCode or array of adUnitCodes * @alias module:pbjs.setTargetingForAst */ pbjsInstance.setTargetingForAst = function (adUnitCodes) { @@ -500,6 +527,9 @@ pbjsInstance.requestBids = (function() { events.emit(REQUEST_BIDS); const cbTimeout = timeout || config.getConfig('bidderTimeout'); logInfo('Invoking $$PREBID_GLOBAL$$.requestBids', arguments); + if (adUnitCodes != null && !Array.isArray(adUnitCodes)) { + adUnitCodes = [adUnitCodes]; + } if (adUnitCodes && adUnitCodes.length) { // if specific adUnitCodes supplied filter adUnits for those codes adUnits = adUnits.filter(unit => includes(adUnitCodes, unit.code)); @@ -507,6 +537,7 @@ pbjsInstance.requestBids = (function() { // otherwise derive adUnitCodes from adUnits adUnitCodes = adUnits && adUnits.map(unit => unit.code); } + adUnitCodes = adUnitCodes.filter(uniques); const ortb2Fragments = { global: mergeDeep({}, config.getAnyConfig('ortb2') || {}, ortb2 || {}), bidder: Object.fromEntries(Object.entries(config.getBidderConfig()).map(([bidder, cfg]) => [bidder, cfg.ortb2]).filter(([_, ortb2]) => ortb2 != null)) @@ -641,7 +672,7 @@ export function executeCallbacks(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 +// 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 tcfControl module is added or not pbjsInstance.requestBids.before(executeCallbacks, 49); /** @@ -867,6 +898,10 @@ pbjsInstance.getHighestCpmBids = function (adUnitCode) { return targeting.getWinningBids(adUnitCode); }; +pbjsInstance.clearAllAuctions = function () { + auctionManager.clearAllAuctions(); +}; + if (FEATURES.VIDEO) { /** * Mark the winning bid as used, should only be used in conjunction with video @@ -876,32 +911,26 @@ if (FEATURES.VIDEO) { * * @alias module:pbjs.markWinningBidAsUsed */ - pbjsInstance.markWinningBidAsUsed = function (markBidRequest) { - const bids = fetchReceivedBids(markBidRequest, 'Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); - + pbjsInstance.markWinningBidAsUsed = function ({adId, adUnitCode, analytics = false}) { + let bids; + if (adUnitCode && adId == null) { + bids = targeting.getWinningBids(adUnitCode); + } else if (adId) { + bids = auctionManager.getBidsReceived().filter(bid => bid.adId === adId) + } else { + logWarn('Improper use of markWinningBidAsUsed. It needs an adUnitCode or an adId to function.'); + } if (bids.length > 0) { - auctionManager.addWinningBid(bids[0]); + if (analytics) { + markWinningBid(bids[0]); + } else { + auctionManager.addWinningBid(bids[0]); + } + markBidAsRendered(bids[0]) } } } -const fetchReceivedBids = (bidRequest, warningMessage) => { - let bids = []; - - if (bidRequest.adUnitCode && bidRequest.adId) { - bids = auctionManager.getBidsReceived() - .filter(bid => bid.adId === bidRequest.adId && bid.adUnitCode === bidRequest.adUnitCode); - } else if (bidRequest.adUnitCode) { - bids = targeting.getWinningBids(bidRequest.adUnitCode); - } else if (bidRequest.adId) { - bids = auctionManager.getBidsReceived().filter(bid => bid.adId === bidRequest.adId); - } else { - logWarn(warningMessage); - } - - return bids; -}; - /** * Get Prebid config options * @param {Object} options @@ -938,12 +967,12 @@ pbjsInstance.que.push(() => listenMessagesFromCreative()); * by prebid once it's done loading. If it runs after prebid loads, then this monkey-patch causes their * function to execute immediately. * - * @memberof pbjs * @param {function} command A function which takes no arguments. This is guaranteed to run exactly once, and only after * the Prebid script has been fully loaded. * @alias module:pbjs.cmd.push + * @alias module:pbjs.que.push */ -pbjsInstance.cmd.push = function (command) { +function quePush(command) { if (typeof command === 'function') { try { command.call(); @@ -953,9 +982,7 @@ pbjsInstance.cmd.push = function (command) { } else { logError('Commands written into $$PREBID_GLOBAL$$.cmd.push must be wrapped in a function'); } -}; - -pbjsInstance.que.push = pbjsInstance.cmd.push; +} function processQueue(queue) { queue.forEach(function (cmd) { @@ -974,6 +1001,8 @@ function processQueue(queue) { * @alias module:pbjs.processQueue */ pbjsInstance.processQueue = function () { + pbjsInstance.que.push = pbjsInstance.cmd.push = quePush; + insertLocatorFrame(); hook.ready(); processQueue(pbjsInstance.que); processQueue(pbjsInstance.cmd); @@ -982,19 +1011,13 @@ pbjsInstance.processQueue = function () { /** * @alias module:pbjs.triggerBilling */ -pbjsInstance.triggerBilling = (winningBid) => { - const bids = fetchReceivedBids(winningBid, 'Improper use of triggerBilling. It requires a bid with at least an adUnitCode or an adId to function.'); - const triggerBillingBid = bids.find(bid => bid.requestId === winningBid.requestId) || bids[0]; - - if (bids.length > 0 && triggerBillingBid) { - try { - adapterManager.callBidBillableBidder(triggerBillingBid); - } catch (e) { - logError('Error when triggering billing :', e); - } - } else { - logWarn('The bid provided to triggerBilling did not match any bids received.'); - } +pbjsInstance.triggerBilling = ({adId, adUnitCode}) => { + auctionManager.getAllWinningBids() + .filter((bid) => bid.adId === adId || (adId == null && bid.adUnitCode === adUnitCode)) + .forEach((bid) => { + adapterManager.triggerBilling(bid); + renderIfDeferred(bid); + }); }; export default pbjsInstance; diff --git a/src/prebid.public.js b/src/prebid.public.js new file mode 100644 index 00000000000..f05e671ac24 --- /dev/null +++ b/src/prebid.public.js @@ -0,0 +1 @@ +export {default} from './prebid.js'; diff --git a/src/refererDetection.js b/src/refererDetection.js index 93ebf085dd5..bfe7fb02671 100644 --- a/src/refererDetection.js +++ b/src/refererDetection.js @@ -34,9 +34,11 @@ export function ensureProtocol(url, win = window) { /** * Extract the domain portion from a URL. - * @param url - * @param noLeadingWww: if true, remove 'www.' appearing at the beginning of the domain. - * @param noPort: if true, do not include the ':[port]' portion + * @param {string} url - The URL to extract the domain from. + * @param {Object} options - Options for parsing the domain. + * @param {boolean} options.noLeadingWww - If true, remove 'www.' appearing at the beginning of the domain. + * @param {boolean} options.noPort - If true, do not include the ':[port]' portion. + * @return {string|undefined} - The extracted domain or undefined if the URL is invalid. */ export function parseDomain(url, {noLeadingWww = false, noPort = false} = {}) { try { @@ -108,13 +110,13 @@ export function detectReferer(win) { * @property {string|null} ref the referrer (document.referrer) to the current page, or null if not available (due to cross-origin restrictions) * @property {string} topmostLocation of the top-most frame for which we could guess the location. Outside of cross-origin scenarios, this is equivalent to `location`. * @property {number} numIframes number of steps between window.self and window.top - * @property {Array[string|null]} stack our best guess at the location for each frame, in the direction top -> self. + * @property {Array} stack our best guess at the location for each frame, in the direction top -> self. */ /** * Walk up the windows to get the origin stack and best available referrer, canonical URL, etc. * - * @returns {refererInfo} + * @returns {refererInfo} An object containing referer information. */ function refererInfo() { const stack = []; diff --git a/src/secureCreatives.js b/src/secureCreatives.js index 96ace0792e4..baa8a82eec7 100644 --- a/src/secureCreatives.js +++ b/src/secureCreatives.js @@ -3,19 +3,22 @@ access to a publisher page from creative payloads. */ -import * as events from './events.js'; import {getAllAssetsMessage, getAssetMessage} from './native.js'; -import { BID_STATUS, EVENTS, MESSAGES } from './constants.js'; +import {BID_STATUS, MESSAGES} from './constants.js'; import {isApnGetTagDefined, isGptPubadsDefined, logError, logWarn} from './utils.js'; -import {auctionManager} from './auctionManager.js'; import {find, includes} from './polyfill.js'; -import {handleCreativeEvent, handleNativeMessage, handleRender} from './adRendering.js'; +import { + deferRendering, + getBidToRender, + handleCreativeEvent, + handleNativeMessage, + handleRender, + markWinner +} from './adRendering.js'; import {getCreativeRendererSource} from './creativeRenderers.js'; const { REQUEST, RESPONSE, NATIVE, EVENT } = MESSAGES; -const BID_WON = EVENTS.BID_WON; - const HANDLER_MAP = { [REQUEST]: handleRenderRequest, [EVENT]: handleEventRequest, @@ -28,7 +31,9 @@ if (FEATURES.NATIVE) { } export function listenMessagesFromCreative() { - window.addEventListener('message', receiveMessage, false); + window.addEventListener('message', function (ev) { + receiveMessage(ev); + }, false); } export function getReplier(ev) { @@ -49,6 +54,12 @@ export function getReplier(ev) { } } +function ensureAdId(adId, reply) { + return function (data, ...args) { + return reply(Object.assign({}, data, {adId}), ...args); + } +} + export function receiveMessage(ev) { var key = ev.message ? 'message' : 'data'; var data = {}; @@ -58,19 +69,19 @@ export function receiveMessage(ev) { return; } - if (data && data.adId && data.message) { - const adObject = find(auctionManager.getBidsReceived(), function (bid) { - return bid.adId === data.adId; - }); - if (HANDLER_MAP.hasOwnProperty(data.message)) { - HANDLER_MAP[data.message](getReplier(ev), data, adObject); - } + if (data && data.adId && data.message && HANDLER_MAP.hasOwnProperty(data.message)) { + return getBidToRender(data.adId, data.message === MESSAGES.REQUEST).then(adObject => { + HANDLER_MAP[data.message](ensureAdId(data.adId, getReplier(ev)), data, adObject); + }) } } -function getResizer(bidResponse) { +function getResizer(adId, bidResponse) { + // in some situations adId !== bidResponse.adId + // the first is the one that was requested and is tied to the element + // the second is the one that is being rendered (sometimes different, e.g. in some paapi setups) return function (width, height) { - resizeRemoteCreative({...bidResponse, width, height}); + resizeRemoteCreative({...bidResponse, width, height, adId}); } } function handleRenderRequest(reply, message, bidResponse) { @@ -81,7 +92,7 @@ function handleRenderRequest(reply, message, bidResponse) { renderer: getCreativeRendererSource(bidResponse) }, adData)); }, - resizeFn: getResizer(bidResponse), + resizeFn: getResizer(message.adId, bidResponse), options: message.options, adId: message.adId, bidResponse @@ -98,21 +109,16 @@ function handleNativeRequest(reply, data, adObject) { logError(`Cannot find ad for x-origin event request: '${data.adId}'`); return; } - - if (adObject.status !== BID_STATUS.RENDERED) { - auctionManager.addWinningBid(adObject); - events.emit(BID_WON, adObject); - } - switch (data.action) { case 'assetRequest': - reply(getAssetMessage(data, adObject)); + deferRendering(adObject, () => reply(getAssetMessage(data, adObject))); break; case 'allAssetRequest': - reply(getAllAssetsMessage(data, adObject)); + deferRendering(adObject, () => reply(getAllAssetsMessage(data, adObject))); break; default: - handleNativeMessage(data, adObject, {resizeFn: getResizer(adObject)}) + handleNativeMessage(data, adObject, {resizeFn: getResizer(data.adId, adObject)}); + markWinner(adObject); } } @@ -141,7 +147,7 @@ export function resizeRemoteCreative({adId, adUnitCode, width, height}) { elementStyle.width = getDimension(width) elementStyle.height = getDimension(height); } else { - logWarn(`Unable to locate matching page element for adUnitCode ${adUnitCode}. Can't resize it to ad's dimensions. Please review setup.`); + logError(`Unable to locate matching page element for adUnitCode ${adUnitCode}. Can't resize it to ad's dimensions. Please review setup.`); } }); diff --git a/src/storageManager.js b/src/storageManager.js index 87d714f77b8..0c0d29dbee4 100644 --- a/src/storageManager.js +++ b/src/storageManager.js @@ -18,6 +18,8 @@ export const STORAGE_TYPE_COOKIES = 'cookie'; export let storageCallbacks = []; +/* eslint-disable prebid/no-global */ + /* * Storage manager constructor. Consumers should prefer one of `getStorageManager` or `getCoreStorageManager`. */ @@ -56,6 +58,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is * If not specified, defaults to the host portion of the current document location. * If a domain is specified, subdomains are always included. * Domain must match the domain of the JavaScript origin. Setting cookies to foreign domains will be silently ignored. + * @param {function} [done] */ const setCookie = function (key, value, expires, sameSite, domain, done) { let cb = function (result) { @@ -64,6 +67,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is const expiresPortion = (expires && expires !== '') ? ` ;expires=${expires}` : ''; const isNone = (sameSite != null && sameSite.toLowerCase() == 'none') const secure = (isNone) ? '; Secure' : ''; + // eslint-disable-next-line prebid/no-member document.cookie = `${key}=${encodeURIComponent(value)}${expiresPortion}; path=/${domainPortion}${sameSite ? `; SameSite=${sameSite}` : ''}${secure}`; } } @@ -72,6 +76,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is /** * @param {string} name + * @param {function} [done] * @returns {(string|null)} */ const getCookie = function(name, done) { @@ -86,27 +91,7 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is }; /** - * @returns {boolean} - */ - const localStorageIsEnabled = function (done) { - let cb = function (result) { - if (result && result.valid) { - try { - localStorage.setItem('prebid.cookieTest', '1'); - return localStorage.getItem('prebid.cookieTest') === '1'; - } catch (error) { - } finally { - try { - localStorage.removeItem('prebid.cookieTest'); - } catch (error) {} - } - } - return false; - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - - /** + * @param {function} [done] * @returns {boolean} */ const cookiesAreEnabled = function (done) { @@ -119,73 +104,84 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is return schedule(cb, STORAGE_TYPE_COOKIES, done); } - /** - * @param {string} key - * @param {string} value - */ - const setDataInLocalStorage = function (key, value, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - window.localStorage.setItem(key, value); - } - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } + function storageMethods(name) { + const capName = name.charAt(0).toUpperCase() + name.substring(1); + const backend = () => window[name]; - /** - * @param {string} key - * @returns {(string|null)} - */ - const getDataFromLocalStorage = function (key, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - return window.localStorage.getItem(key); - } - return null; - } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - - /** - * @param {string} key - */ - const removeDataFromLocalStorage = function (key, done) { - let cb = function (result) { - if (result && result.valid && hasLocalStorage()) { - window.localStorage.removeItem(key); + const hasStorage = function (done) { + let cb = function (result) { + if (result && result.valid) { + try { + return !!backend(); + } catch (e) { + logError(`${name} api disabled`); + } + } + return false; } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); - } - /** - * @returns {boolean} - */ - const hasLocalStorage = function (done) { - let cb = function (result) { - if (result && result.valid) { - try { - return !!window.localStorage; - } catch (e) { - logError('Local storage api disabled'); + return { + [`has${capName}`]: hasStorage, + [`${name}IsEnabled`](done) { + let cb = function (result) { + if (result && result.valid) { + try { + backend().setItem('prebid.cookieTest', '1'); + return backend().getItem('prebid.cookieTest') === '1'; + } catch (error) { + } finally { + try { + backend().removeItem('prebid.cookieTest'); + } catch (error) {} + } + } + return false; } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`setDataIn${capName}`](key, value, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + backend().setItem(key, value); + } + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`getDataFrom${capName}`](key, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + return backend().getItem(key); + } + return null; + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); + }, + [`removeDataFrom${capName}`](key, done) { + let cb = function (result) { + if (result && result.valid && hasStorage()) { + backend().removeItem(key); + } + } + return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } - return false; } - return schedule(cb, STORAGE_TYPE_LOCALSTORAGE, done); } /** * Returns all cookie values from the jar whose names contain the `keyLike` * Needs to exist in `utils.js` as it follows the StorageHandler interface defined in live-connect-js. If that module were to be removed, this function can go as well. * @param {string} keyLike - * @return {[]} + * @param {function} [done] + * @returns {string[]} */ const findSimilarCookies = function(keyLike, done) { let cb = function (result) { if (result && result.valid) { const all = []; if (hasDeviceAccess()) { + // eslint-disable-next-line prebid/no-member const cookies = document.cookie.split(';'); while (cookies.length) { const cookie = cookies.pop(); @@ -207,12 +203,9 @@ export function newStorageManager({moduleName, moduleType} = {}, {isAllowed = is return { setCookie, getCookie, - localStorageIsEnabled, cookiesAreEnabled, - setDataInLocalStorage, - getDataFromLocalStorage, - removeDataFromLocalStorage, - hasLocalStorage, + ...storageMethods('localStorage'), + ...storageMethods('sessionStorage'), findSimilarCookies } } diff --git a/src/targeting.js b/src/targeting.js index acb3ddb09ff..aecc29787c7 100644 --- a/src/targeting.js +++ b/src/targeting.js @@ -1,3 +1,21 @@ +import { auctionManager } from './auctionManager.js'; +import { getTTL } from './bidTTL.js'; +import { bidderSettings } from './bidderSettings.js'; +import { config } from './config.js'; +import { + BID_STATUS, + DEFAULT_TARGETING_KEYS, + EVENTS, + JSON_MAPPING, + NATIVE_KEYS, + STATUS, + TARGETING_KEYS +} from './constants.js'; +import * as events from './events.js'; +import { hook } from './hook.js'; +import { ADPOD } from './mediaTypes.js'; +import { NATIVE_TARGETING_KEYS } from './native.js'; +import { find, includes } from './polyfill.js'; import { deepAccess, deepClone, @@ -11,19 +29,11 @@ import { logInfo, logMessage, logWarn, + sortByHighestCpm, timestamp, uniques, } from './utils.js'; -import {config} from './config.js'; -import {NATIVE_TARGETING_KEYS} from './native.js'; -import {auctionManager} from './auctionManager.js'; -import {ADPOD} from './mediaTypes.js'; -import {hook} from './hook.js'; -import {bidderSettings} from './bidderSettings.js'; -import {find, includes} from './polyfill.js'; -import { BID_STATUS, JSON_MAPPING, DEFAULT_TARGETING_KEYS, TARGETING_KEYS, NATIVE_KEYS, STATUS } from './constants.js'; -import {getHighestCpm, getOldestHighestCpmBid} from './utils/reducers.js'; -import {getTTL} from './bidTTL.js'; +import { getHighestCpm, getOldestHighestCpmBid } from './utils/reducers.js'; var pbTargetingKeys = []; @@ -58,7 +68,7 @@ export function isBidUsable(bid) { // 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 const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, highestCpmCallback, adUnitBidLimit = 0, hasModified = false) { +export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, winReducer, adUnitBidLimit = 0, hasModified = false, winSorter = sortByHighestCpm) { if (!hasModified) { const bids = []; const dealPrioritization = config.getConfig('sendBidsControl.dealPrioritization'); @@ -67,13 +77,14 @@ export const getHighestCpmBidsFromBidPool = hook('sync', function(bidsReceived, // 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))); + let bidsByBidder = groupBy(buckets[bucketKey], 'bidderCode') + Object.keys(bidsByBidder).forEach(key => { bucketBids.push(bidsByBidder[key].reduce(winReducer)) }); // if adUnitBidLimit is set, pass top N number bids - if (adUnitBidLimit > 0) { + if (adUnitBidLimit) { bucketBids = dealPrioritization ? bucketBids.sort(sortByDealAndPriceBucketOrCpm(true)) : bucketBids.sort((a, b) => b.cpm - a.cpm); bids.push(...bucketBids.slice(0, adUnitBidLimit)); } else { + bucketBids = bucketBids.sort(winSorter) bids.push(...bucketBids); } }); @@ -124,6 +135,33 @@ export function sortByDealAndPriceBucketOrCpm(useCpm = false) { } } +/** + * Return a map where each code in `adUnitCodes` maps to a list of GPT slots that match it. + * + * @param {Array} adUnitCodes + * @param customSlotMatching + * @param getSlots + * @return {Object.} + */ +export function getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching, getSlots = () => window.googletag.pubads().getSlots()) { + return getSlots().reduce((auToSlots, slot) => { + const customMatch = isFn(customSlotMatching) && customSlotMatching(slot); + Object.keys(auToSlots).filter(isFn(customMatch) ? customMatch : isAdUnitCodeMatchingSlot(slot)).forEach(au => auToSlots[au].push(slot)); + return auToSlots; + }, Object.fromEntries(adUnitCodes.map(au => [au, []]))); +} + +/** + * Clears targeting for bids + */ +function clearTargeting(slot) { + pbTargetingKeys.forEach(key => { + if (slot.getTargeting(key)) { + slot.clearTargeting(key) + } + }) +} + /** * @typedef {Object.} targeting * @property {string} targeting_key @@ -144,22 +182,11 @@ export function newTargeting(auctionManager) { targeting.resetPresetTargeting = function(adUnitCode, customSlotMatching) { if (isGptPubadsDefined()) { const adUnitCodes = getAdUnitCodes(adUnitCode); - const adUnits = auctionManager.getAdUnits().filter(adUnit => includes(adUnitCodes, adUnit.code)); - let unsetKeys = pbTargetingKeys.reduce((reducer, key) => { - reducer[key] = null; - return reducer; - }, {}); - window.googletag.pubads().getSlots().forEach(slot => { - let customSlotMatchingFunc = isFn(customSlotMatching) && customSlotMatching(slot); - // reset only registered adunits - adUnits.forEach(unit => { - if (unit.code === slot.getAdUnitPath() || - unit.code === slot.getSlotElementId() || - (isFn(customSlotMatchingFunc) && customSlotMatchingFunc(unit.code))) { - slot.updateTargetingFromMap(unsetKeys); - } - }); - }); + Object.values(getGPTSlotsForAdUnits(adUnitCodes, customSlotMatching)).forEach((slots) => { + slots.forEach(slot => { + clearTargeting(slot) + }) + }) } }; @@ -180,39 +207,35 @@ export function newTargeting(auctionManager) { }); }; - /** - * checks if bid has targeting set and belongs based on matching ad unit codes - * @return {boolean} true or false - */ - function bidShouldBeAddedToTargeting(bid, adUnitCodes) { - return bid.adserverTargeting && adUnitCodes && - ((isArray(adUnitCodes) && includes(adUnitCodes, bid.adUnitCode)) || - (typeof adUnitCodes === 'string' && bid.adUnitCode === adUnitCodes)); - }; + function addBidToTargeting(bids, bidderLevelTargetingEnabled = false, deals = false) { + if (!bidderLevelTargetingEnabled) return []; - /** - * Returns targeting for any bids which have deals if alwaysIncludeDeals === true - */ - function getDealBids(adUnitCodes, bidsReceived) { - if (config.getConfig('targetingControls.alwaysIncludeDeals') === true) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); - - // we only want the top bid from bidders who have multiple entries per ad unit code - const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm); - - // populate targeting keys for the remaining bids if they have a dealId - return bids.map(bid => { - if (bid.dealId && bidShouldBeAddedToTargeting(bid, adUnitCodes)) { - return { - [bid.adUnitCode]: getTargetingMap(bid, standardKeys.filter( - key => typeof bid.adserverTargeting[key] !== 'undefined') - ) - }; + const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); + const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); + + const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys + ? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key]) + : standardKeys; + + return bids.reduce((result, bid) => { + if ((!deals || bid.dealId)) { + const targetingValue = getTargetingMap(bid, standardKeys.filter( + key => typeof bid.adserverTargeting[key] !== 'undefined' && + (deals || allowedSendAllBidTargeting.indexOf(key) !== -1))); + + if (targetingValue) { + result.push({[bid.adUnitCode]: targetingValue}) } - }).filter(bid => bid); // removes empty elements in array - } - return []; - }; + } + return result; + }, []); + } + + function getBidderTargeting(bids) { + const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals'); + const bidderLevelTargetingEnabled = config.getConfig('enableSendAllBids') || alwaysIncludeDeals; + return addBidToTargeting(bids, bidderLevelTargetingEnabled, alwaysIncludeDeals); + } /** * Returns filtered ad server targeting for custom and allowed keys. @@ -261,26 +284,15 @@ export function newTargeting(auctionManager) { * @param {string=} adUnitCode * @return {Object.} targeting */ - targeting.getAllTargeting = function(adUnitCode, bidsReceived = getBidsReceived()) { + targeting.getAllTargeting = function(adUnitCode, bidLimit, bidsReceived, winReducer = getHighestCpm, winSorter = sortByHighestCpm) { + bidsReceived ||= getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); - - // Get targeting for the winning bid. Add targeting for any bids that have - // `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(getAdUnitTargeting(adUnitCodes)); - - // store a reference of the targeting keys - targeting.map(adUnitCode => { - Object.keys(adUnitCode).map(key => { - adUnitCode[key].map(targetKey => { - if (pbTargetingKeys.indexOf(Object.keys(targetKey)[0]) === -1) { - pbTargetingKeys = Object.keys(targetKey).concat(pbTargetingKeys); - } - }); - }); - }); + const sendAllBids = config.getConfig('enableSendAllBids'); + const bidLimitConfigValue = config.getConfig('sendBidsControl.bidLimit'); + const adUnitBidLimit = (sendAllBids && (bidLimit || bidLimitConfigValue)) || 0; + const { customKeysByUnit, filteredBids } = getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived); + const bidsSorted = getHighestCpmBidsFromBidPool(filteredBids, winReducer, adUnitBidLimit, undefined, winSorter); + let targeting = getTargetingLevels(bidsSorted, customKeysByUnit); const defaultKeys = Object.keys(Object.assign({}, DEFAULT_TARGETING_KEYS, NATIVE_KEYS)); let allowedKeys = config.getConfig(CFG_ALLOW_TARGETING_KEYS); @@ -316,6 +328,63 @@ export function newTargeting(auctionManager) { return targeting; }; + function updatePBTargetingKeys(adUnitCode) { + (Object.keys(adUnitCode)).forEach(key => { + adUnitCode[key].forEach(targetKey => { + const targetKeys = Object.keys(targetKey); + if (pbTargetingKeys.indexOf(targetKeys[0]) === -1) { + pbTargetingKeys = targetKeys.concat(pbTargetingKeys); + } + }); + }); + } + + function getTargetingLevels(bidsSorted, customKeysByUnit) { + const targeting = getWinningBidTargeting(bidsSorted) + .concat(getCustomBidTargeting(bidsSorted, customKeysByUnit)) + .concat(getBidderTargeting(bidsSorted)) + .concat(getAdUnitTargeting()); + + targeting.forEach(adUnitCode => { + updatePBTargetingKeys(adUnitCode); + }); + + return targeting; + } + + function getfilteredBidsAndCustomKeys(adUnitCodes, bidsReceived) { + const filteredBids = []; + const customKeysByUnit = {}; + const alwaysIncludeDeals = config.getConfig('targetingControls.alwaysIncludeDeals'); + + bidsReceived.forEach(bid => { + const adUnitIsEligible = includes(adUnitCodes, bid.adUnitCode); + const cpmAllowed = bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; + const isPreferredDeal = alwaysIncludeDeals && bid.dealId; + + if (adUnitIsEligible && (isPreferredDeal || cpmAllowed)) { + filteredBids.push(bid); + Object.keys(bid.adserverTargeting) + .filter(getCustomKeys()) + .forEach(key => { + const truncKey = key.substring(0, MAX_DFP_KEYLENGTH); + const data = customKeysByUnit[bid.adUnitCode] || {}; + const value = [bid.adserverTargeting[key]]; + + if (data[truncKey]) { + data[truncKey] = data[truncKey].concat(value).filter(uniques); + } else { + data[truncKey] = value; + } + + customKeysByUnit[bid.adUnitCode] = data; + }) + } + }); + + return {filteredBids, customKeysByUnit}; + } + // warn about conflicting configuration config.getConfig('targetingControls', function (config) { if (deepAccess(config, CFG_ALLOW_TARGETING_KEYS) != null && deepAccess(config, CFG_ADD_TARGETING_KEYS) != null) { @@ -407,35 +476,56 @@ export function newTargeting(auctionManager) { }; }).reduce((p, c) => Object.assign(c, p), {}) }; - }).reduce(function (accumulator, targeting) { + }) + + targetingObj = targetingObj.reduce(function (accumulator, targeting) { var key = Object.keys(targeting)[0]; accumulator[key] = Object.assign({}, accumulator[key], targeting[key]); return accumulator; }, {}); + return targetingObj; } - /** - * Sets targeting for DFP - * @param {Object.>} targetingConfig - */ - targeting.setTargetingForGPT = function(targetingConfig, customSlotMatching) { - window.googletag.pubads().getSlots().forEach(slot => { - Object.keys(targetingConfig).filter(customSlotMatching ? customSlotMatching(slot) : isAdUnitCodeMatchingSlot(slot)) - .forEach(targetId => { - Object.keys(targetingConfig[targetId]).forEach(key => { - let value = targetingConfig[targetId][key]; - if (typeof value === 'string' && value.indexOf(',') !== -1) { - // due to the check the array will be formed only if string has ',' else plain string will be assigned as value - value = value.split(','); - } - targetingConfig[targetId][key] = value; - }); - logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingConfig[targetId]); - slot.updateTargetingFromMap(targetingConfig[targetId]) - }) + targeting.setTargetingForGPT = hook('sync', function (adUnit, customSlotMatching) { + // get our ad unit codes + let targetingSet = targeting.getAllTargeting(adUnit); + + let resetMap = Object.fromEntries(pbTargetingKeys.map(key => [key, null])); + + Object.entries(getGPTSlotsForAdUnits(Object.keys(targetingSet), customSlotMatching)).forEach(([targetId, slots]) => { + slots.forEach(slot => { + // now set new targeting keys + Object.keys(targetingSet[targetId]).forEach(key => { + let value = targetingSet[targetId][key]; + if (typeof value === 'string' && value.indexOf(',') !== -1) { + // due to the check the array will be formed only if string has ',' else plain string will be assigned as value + value = value.split(','); + } + targetingSet[targetId][key] = value; + }); + logMessage(`Attempting to set targeting-map for slot: ${slot.getSlotElementId()} with targeting-map:`, targetingSet[targetId]); + slot.updateTargetingFromMap(Object.assign({}, resetMap, targetingSet[targetId])) + }) }) - }; + + Object.keys(targetingSet).forEach((adUnitCode) => { + Object.keys(targetingSet[adUnitCode]).forEach((targetingKey) => { + if (targetingKey === 'hb_adid') { + auctionManager.setStatusForBids(targetingSet[adUnitCode][targetingKey], BID_STATUS.BID_TARGETING_SET); + } + }); + }); + + targeting.targetingDone(targetingSet); + + // emit event + events.emit(EVENTS.SET_TARGETING, targetingSet); + }, 'setTargetingForGPT'); + + targeting.targetingDone = hook('sync', function (targetingSet) { + return targetingSet; + }, 'targetingDone'); /** * normlizes input to a `adUnit.code` array @@ -451,49 +541,54 @@ export function newTargeting(auctionManager) { return auctionManager.getAdUnitCodes() || []; } - function getBidsReceived() { - let bidsReceived = auctionManager.getBidsReceived(); - - if (!config.getConfig('useBidCache')) { - // don't use bid cache (i.e. filter out bids not in the latest auction) - bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId) - } else { - // if custom bid cache filter function exists, run for each bid from - // previous auctions. If it returns true, include bid in bid pool + function getBidsReceived(winReducer = getOldestHighestCpmBid, winSorter = undefined) { + let bidsReceived = auctionManager.getBidsReceived().reduce((bids, bid) => { + const bidCacheEnabled = config.getConfig('useBidCache'); const filterFunction = config.getConfig('bidCacheFilterFunction'); - if (typeof filterFunction === 'function') { - bidsReceived = bidsReceived.filter(bid => latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId || !!filterFunction(bid)) - } - } + const isBidFromLastAuction = latestAuctionForAdUnit[bid.adUnitCode] === bid.auctionId; + const filterFunctionResult = bidCacheEnabled && !isBidFromLastAuction && typeof filterFunction === 'function' ? !!filterFunction(bid) : true; + const cacheFilter = bidCacheEnabled || isBidFromLastAuction; + const bidFilter = cacheFilter && filterFunctionResult; - bidsReceived = bidsReceived - .filter(bid => deepAccess(bid, 'video.context') !== ADPOD) - .filter(isBidUsable); - - bidsReceived - .forEach(bid => { + if (bidFilter && deepAccess(bid, 'video.context') !== ADPOD && isBidUsable(bid)) { bid.latestTargetedAuctionId = latestAuctionForAdUnit[bid.adUnitCode]; - return bid; - }); + bids.push(bid) + } + + return bids; + }, []); - return getHighestCpmBidsFromBidPool(bidsReceived, getOldestHighestCpmBid); + return getHighestCpmBidsFromBidPool(bidsReceived, winReducer, undefined, undefined, undefined, winSorter); } /** * Returns top bids for a given adUnit or set of adUnits. * @param {(string|string[])} adUnitCode adUnitCode or array of adUnitCodes - * @return {[type]} [description] + * @param {(Array|undefined)} bids - The received bids, defaulting to the result of getBidsReceived(). + * @param {function(Array): Array} [winReducer = getHighestCpm] - reducer method + * @param {function(Array): Array} [winSorter = sortByHighestCpm] - sorter method + * @return {Array} - An array of winning bids. */ - targeting.getWinningBids = function(adUnitCode, bidsReceived = getBidsReceived()) { + targeting.getWinningBids = function(adUnitCode, bids, winReducer = getHighestCpm, winSorter = sortByHighestCpm) { + const usedCodes = []; + const bidsReceived = bids || getBidsReceived(winReducer, winSorter); const adUnitCodes = getAdUnitCodes(adUnitCode); + return bidsReceived - .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .filter(bid => (bidderSettings.get(bid.bidderCode, 'allowZeroCpmBids') === true) ? bid.cpm >= 0 : bid.cpm > 0) - .map(bid => bid.adUnitCode) - .filter(uniques) - .map(adUnitCode => bidsReceived - .filter(bid => bid.adUnitCode === adUnitCode ? bid : null) - .reduce(getHighestCpm)); + .reduce((result, bid) => { + const code = bid.adUnitCode; + const cpmEligible = bidderSettings.get(code, 'allowZeroCpmBids') === true ? bid.cpm >= 0 : bid.cpm > 0; + const isPreferredDeal = config.getConfig('targetingControls.alwaysIncludeDeals') && bid.dealId; + const eligible = includes(adUnitCodes, code) && + !includes(usedCodes, code) && + (isPreferredDeal || cpmEligible) + if (eligible) { + result.push(bid); + usedCodes.push(code); + } + + return result; + }, []); }; /** @@ -530,11 +625,20 @@ export function newTargeting(auctionManager) { /** * Get targeting key value pairs for winning bid. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} winning bids targeting + * @param {Array} bidsReceived code array + * @return {targetingArray} winning bids targeting */ - function getWinningBidTargeting(adUnitCodes, bidsReceived) { - let winners = targeting.getWinningBids(adUnitCodes, bidsReceived); + function getWinningBidTargeting(bidsReceived) { + let usedAdUnitCodes = []; + let winners = bidsReceived + .reduce((bids, bid) => { + if (!includes(usedAdUnitCodes, bid.adUnitCode)) { + bids.push(bid); + usedAdUnitCodes.push(bid.adUnitCode); + } + return bids; + }, []); + let standardKeys = getStandardKeys(); winners = winners.map(winner => { @@ -566,44 +670,6 @@ export function newTargeting(auctionManager) { .concat(TARGETING_KEYS_ARR).filter(uniques); // standard keys defined in the library. } - /** - * Merge custom adserverTargeting with same key name for same adUnitCode. - * e.g: Appnexus defining custom keyvalue pair foo:bar and Rubicon defining custom keyvalue pair foo:baz will be merged to foo: ['bar','baz'] - * - * @param {Object[]} acc Accumulator for reducer. It will store updated bidResponse objects - * @param {Object} bid BidResponse - * @param {number} index current index - * @param {Array} arr original array - */ - function mergeAdServerTargeting(acc, bid, index, arr) { - function concatTargetingValue(key) { - return function(currentBidElement) { - if (!isArray(currentBidElement.adserverTargeting[key])) { - currentBidElement.adserverTargeting[key] = [currentBidElement.adserverTargeting[key]]; - } - currentBidElement.adserverTargeting[key] = currentBidElement.adserverTargeting[key].concat(bid.adserverTargeting[key]).filter(uniques); - delete bid.adserverTargeting[key]; - } - } - - function hasSameAdunitCodeAndKey(key) { - return function(currentBidElement) { - return currentBidElement.adUnitCode === bid.adUnitCode && currentBidElement.adserverTargeting[key] - } - } - - Object.keys(bid.adserverTargeting) - .filter(getCustomKeys()) - .forEach(key => { - if (acc.length) { - acc.filter(hasSameAdunitCodeAndKey(key)) - .forEach(concatTargetingValue(key)); - } - }); - acc.push(bid); - return acc; - } - function getCustomKeys() { let standardKeys = getStandardKeys(); if (FEATURES.NATIVE) { @@ -614,71 +680,42 @@ export function newTargeting(auctionManager) { } } - function truncateCustomKeys(bid) { - return { - [bid.adUnitCode]: Object.keys(bid.adserverTargeting) - // Get only the non-standard keys of the losing bids, since we - // don't want to override the standard keys of the winning bid. - .filter(getCustomKeys()) - .map(key => { - return { - [key.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] - }; - }) - } - } - /** * Get custom targeting key value pairs for bids. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} bids with custom targeting defined in bidderSettings + * @param {Array} bidsSorted code array + * @param {Object} customKeysByUnit code array + * @return {targetingArray} bids with custom targeting defined in bidderSettings */ - function getCustomBidTargeting(adUnitCodes, bidsReceived) { - return bidsReceived - .filter(bid => includes(adUnitCodes, bid.adUnitCode)) - .map(bid => Object.assign({}, bid)) - .reduce(mergeAdServerTargeting, []) - .map(truncateCustomKeys) - .filter(bid => bid); // removes empty elements in array; - } - - /** - * Get targeting key value pairs for non-winning bids. - * @param {string[]} adUnitCodes code array - * @return {targetingArray} all non-winning bids targeting - */ - function getBidLandscapeTargeting(adUnitCodes, bidsReceived) { - const standardKeys = FEATURES.NATIVE ? TARGETING_KEYS_ARR.concat(NATIVE_TARGETING_KEYS) : TARGETING_KEYS_ARR.slice(); - const adUnitBidLimit = config.getConfig('sendBidsControl.bidLimit'); - const bids = getHighestCpmBidsFromBidPool(bidsReceived, getHighestCpm, adUnitBidLimit); - const allowSendAllBidsTargetingKeys = config.getConfig('targetingControls.allowSendAllBidsTargetingKeys'); + function getCustomBidTargeting(bidsSorted, customKeysByUnit) { + return bidsSorted + .reduce((acc, bid) => { + const newBid = Object.assign({}, bid); + const customKeysForUnit = customKeysByUnit[newBid.adUnitCode]; + const targeting = []; + + if (customKeysForUnit) { + Object.keys(customKeysForUnit).forEach(key => { + if (key && customKeysForUnit[key]) targeting.push({[key]: customKeysForUnit[key]}); + }) + } - const allowedSendAllBidTargeting = allowSendAllBidsTargetingKeys - ? allowSendAllBidsTargetingKeys.map((key) => TARGETING_KEYS[key]) - : standardKeys; + acc.push({[newBid.adUnitCode]: targeting}); - // 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' && - allowedSendAllBidTargeting.indexOf(key) !== -1) - ) - }; - } - }).filter(bid => bid); // removes empty elements in array + return acc; + }, []); } function getTargetingMap(bid, keys) { - return keys.map(key => { - return { - [`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]] - }; - }); + return keys.reduce((targeting, key) => { + const value = bid.adserverTargeting[key]; + if (value) { + targeting.push({[`${key}_${bid.bidderCode}`.substring(0, MAX_DFP_KEYLENGTH)]: [bid.adserverTargeting[key]]}) + } + return targeting; + }, []); } - function getAdUnitTargeting(adUnitCodes) { + function getAdUnitTargeting() { function getTargetingObj(adUnit) { return deepAccess(adUnit, JSON_MAPPING.ADSERVER_TARGETING); } @@ -695,10 +732,13 @@ export function newTargeting(auctionManager) { } return auctionManager.getAdUnits() - .filter(adUnit => includes(adUnitCodes, adUnit.code) && getTargetingObj(adUnit)) - .map(adUnit => { - return {[adUnit.code]: getTargetingValues(adUnit)} - }); + .filter(adUnit => getTargetingObj(adUnit)) + .reduce((result, adUnit) => { + const targetingValues = getTargetingValues(adUnit); + + if (targetingValues)result.push({[adUnit.code]: targetingValues}); + return result; + }, []); } targeting.isApntagDefined = function() { diff --git a/src/userSync.js b/src/userSync.js index 1b684de6de0..d8f2238007d 100644 --- a/src/userSync.js +++ b/src/userSync.js @@ -25,7 +25,7 @@ export const USERSYNC_DEFAULT_CONFIG = { }, syncsPerBidder: 5, syncDelay: 3000, - auctionDelay: 0 + auctionDelay: 500 }; // Set userSync default values diff --git a/src/utils.js b/src/utils.js index 1747622b9c9..015b1142d47 100644 --- a/src/utils.js +++ b/src/utils.js @@ -1,11 +1,12 @@ import {config} from './config.js'; import {klona} from 'klona/json'; import {includes} from './polyfill.js'; -import { EVENTS, S2S } from './constants.js'; +import {EVENTS} from './constants.js'; import {GreedyPromise} from './utils/promise.js'; import {getGlobal} from './prebidGlobal.js'; +import { default as deepAccess } from 'dlv/index.js'; -export { default as deepAccess } from 'dlv/index.js'; +export { deepAccess }; export { dset as deepSetValue } from 'dset'; var tStr = 'String'; @@ -41,6 +42,7 @@ export const internal = { createTrackPixelIframeHtml, getWindowSelf, getWindowTop, + canAccessWindowTop, getWindowLocation, insertUserSyncIframe, insertElement, @@ -129,37 +131,55 @@ export function transformAdServerTargetingObj(targeting) { } /** - * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' - * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] - * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of width, height tuples `[[300, 250]]` or '[[300,250], [970,90]]' */ -export function parseSizesInput(sizeObj) { - if (typeof sizeObj === 'string') { +export function sizesToSizeTuples(sizes) { + if (typeof sizes === 'string') { // multiple sizes will be comma-separated - return sizeObj.split(',').filter(sz => sz.match(/^(\d)+x(\d)+$/i)) - } else if (typeof sizeObj === 'object') { - if (sizeObj.length === 2 && typeof sizeObj[0] === 'number' && typeof sizeObj[1] === 'number') { - return [parseGPTSingleSizeArray(sizeObj)]; - } else { - return sizeObj.map(parseGPTSingleSizeArray) + return sizes + .split(/\s*,\s*/) + .map(sz => sz.match(/^(\d+)x(\d+)$/i)) + .filter(match => match) + .map(([_, w, h]) => [parseInt(w, 10), parseInt(h, 10)]) + } else if (Array.isArray(sizes)) { + if (isValidGPTSingleSize(sizes)) { + return [sizes] } + return sizes.filter(isValidGPTSingleSize); } return []; } +/** + * Parse a GPT-Style general size Array like `[[300, 250]]` or `"300x250,970x90"` into an array of sizes `["300x250"]` or '['300x250', '970x90']' + * @param {(Array.|Array.)} sizeObj Input array or double array [300,250] or [[300,250], [728,90]] + * @return {Array.} Array of strings like `["300x250"]` or `["300x250", "728x90"]` + */ +export function parseSizesInput(sizeObj) { + return sizesToSizeTuples(sizeObj).map(sizeTupleToSizeString); +} + +export function sizeTupleToSizeString(size) { + return size[0] + 'x' + size[1] +} + // Parse a GPT style single size array, (i.e [300, 250]) // into an AppNexus style string, (i.e. 300x250) export function parseGPTSingleSizeArray(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return singleSize[0] + 'x' + singleSize[1]; + return sizeTupleToSizeString(singleSize); } } +export function sizeTupleToRtbSize(size) { + return {w: size[0], h: size[1]}; +} + // Parse a GPT style single size array, (i.e [300, 250]) // into OpenRTB-compatible (imp.banner.w/h, imp.banner.format.w/h, imp.video.w/h) object(i.e. {w:300, h:250}) export function parseGPTSingleSizeArrayToRtbSize(singleSize) { if (isValidGPTSingleSize(singleSize)) { - return {w: singleSize[0], h: singleSize[1]}; + return sizeTupleToRtbSize(singleSize) } } @@ -180,6 +200,16 @@ export function getWindowLocation() { return window.location; } +export function canAccessWindowTop() { + try { + if (internal.getWindowTop().location.href) { + return true; + } + } catch (e) { + return false; + } +} + /** * Wrappers to console.(log | info | warn | error). Takes N arguments, the same as the native methods */ @@ -353,7 +383,8 @@ export function isEmptyStr(str) { * Iterate object with the function * falls back to es5 `forEach` * @param {Array|Object} object - * @param {Function(value, key, object)} fn + * @param {Function} fn - The function to execute for each element. It receives three arguments: value, key, and the original object. + * @returns {void} */ export function _each(object, fn) { if (isFn(object?.forEach)) return object.forEach(fn, this); @@ -368,7 +399,7 @@ export function contains(a, obj) { * Map an array or object into another array * given a function * @param {Array|Object} object - * @param {Function(value, key, object)} callback + * @param {Function} callback - The function to execute for each element. It receives three arguments: value, key, and the original object. * @return {Array} */ export function _map(object, callback) { @@ -443,12 +474,6 @@ export function triggerPixel(url, done, timeout) { img.src = url; } -export function callBurl({ source, burl }) { - if (source === S2S.SRC && burl) { - internal.triggerPixel(burl); - } -} - /** * Inserts an empty iframe with the specified `html`, primarily used for tracking purposes * (though could be for other purposes) @@ -471,7 +496,6 @@ export function insertHtmlIntoIframe(htmlCode) { /** * Inserts empty iframe with the specified `url` for cookie sync * @param {string} url URL to be requested - * @param {string} encodeUri boolean if URL should be encoded before inserted. Defaults to true * @param {function} [done] an optional exit callback, used when this usersync pixel is added during an async process * @param {Number} [timeout] an optional timeout in milliseconds for the iframe to load before calling `done` */ @@ -582,6 +606,10 @@ export function isApnGetTagDefined() { } } +export const sortByHighestCpm = (a, b) => { + return b.cpm - a.cpm; +} + /** * Fisher–Yates shuffle * http://stackoverflow.com/a/6274398 @@ -620,6 +648,33 @@ export function inIframe() { } } +/** + * https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf + */ +export function isSafeFrameWindow() { + if (!inIframe()) { + return false; + } + + const ws = internal.getWindowSelf(); + return !!(ws.$sf && ws.$sf.ext); +} + +/** + * Returns the result of calling the function $sf.ext.geom() if it exists + * @see https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf — 5.4 Function $sf.ext.geom + * @returns {Object | undefined} geometric information about the container + */ +export function getSafeframeGeometry() { + try { + const ws = getWindowSelf(); + return (typeof ws.$sf.ext.geom === 'function') ? ws.$sf.ext.geom() : undefined; + } catch (e) { + logError('Error getting SafeFrame geometry', e); + return undefined; + } +} + export function isSafariBrowser() { return /^((?!chrome|android|crios|fxios).)*safari/i.test(navigator.userAgent); } @@ -652,6 +707,33 @@ export function getPerformanceNow() { return (window.performance && window.performance.now && window.performance.now()) || 0; } +/** + * Retuns the difference between `timing.domLoading` and `timing.navigationStart`. + * This function uses the deprecated `Performance.timing` API and should be removed in future. + * It has not been updated yet because it is still used in some modules. + * @deprecated + * @param {Window} w The window object used to perform the api call. default to window.self + * @returns {number} + */ +export function getDomLoadingDuration(w) { + let domLoadingDuration = -1; + + w = w || getWindowSelf(); + + const performance = w.performance; + + if (w.performance?.timing) { + if (w.performance.timing.navigationStart > 0) { + const val = performance.timing.domLoading - performance.timing.navigationStart; + if (val > 0) { + domLoadingDuration = val; + } + } + } + + return domLoadingDuration; +} + /** * When the deviceAccess flag config option is false, no cookies should be read or set * @returns {boolean} @@ -664,6 +746,7 @@ export function hasDeviceAccess() { * @returns {(boolean|undefined)} */ export function checkCookieSupport() { + // eslint-disable-next-line prebid/no-member if (window.navigator.cookieEnabled || !!document.cookie.length) { return true; } @@ -696,7 +779,6 @@ export function delayExecution(func, numRequiredCalls) { /** * https://stackoverflow.com/a/34890276/428704 - * @export * @param {Array} xs * @param {string} key * @returns {Object} {${key_value}: ${groupByArray}, key_value: {groupByArray}} @@ -912,9 +994,9 @@ export function buildUrl(obj) { * This function deeply compares two objects checking for their equivalence. * @param {Object} obj1 * @param {Object} obj2 - * @param checkTypes {boolean} if set, two objects with identical properties but different constructors will *not* - * be considered equivalent. - * @returns {boolean} + * @param {Object} [options] - Options for comparison. + * @param {boolean} [options.checkTypes=false] - If set, two objects with identical properties but different constructors will *not* be considered equivalent. + * @returns {boolean} - Returns `true` if the objects are equivalent, `false` otherwise. */ export function deepEqual(obj1, obj2, {checkTypes = false} = {}) { if (obj1 === obj2) return true; @@ -1026,6 +1108,14 @@ export function safeJSONParse(data) { } catch (e) {} } +export function safeJSONEncode(data) { + try { + return JSON.stringify(data); + } catch (e) { + return ''; + } +} + /** * Returns a memoized version of `fn`. * @@ -1047,9 +1137,36 @@ export function memoize(fn, key = function (arg) { return arg; }) { return memoized; } +/** + * Returns a Unix timestamp for given time value and unit. + * @param {number} timeValue numeric value, defaults to 0 (which means now) + * @param {string} timeUnit defaults to days (or 'd'), use 'm' for minutes. Any parameter that isn't 'd' or 'm' will return Date.now(). + * @returns {number} + */ +export function getUnixTimestampFromNow(timeValue = 0, timeUnit = 'd') { + const acceptableUnits = ['m', 'd']; + if (acceptableUnits.indexOf(timeUnit) < 0) { + return Date.now(); + } + const multiplication = timeValue / (timeUnit === 'm' ? 1440 : 1); + return Date.now() + (timeValue && timeValue > 0 ? (1000 * 60 * 60 * 24 * multiplication) : 0); +} + +/** + * Converts given object into an array, so {key: 1, anotherKey: 'fred', third: ['fred']} is turned + * into [{key: 1}, {anotherKey: 'fred'}, {third: ['fred']}] + * @param {Object} obj the object + * @returns {Array} + */ +export function convertObjectToArray(obj) { + return Object.keys(obj).map(key => { + return {[key]: obj[key]}; + }); +} + /** * Sets dataset attributes on a script - * @param {Script} script + * @param {HTMLScriptElement} script * @param {object} attributes */ export function setScriptAttributes(script, attributes) { @@ -1084,28 +1201,86 @@ export function binarySearch(arr, el, key = (el) => el) { return left; } -const skipPatterns = [ - /^\[object HTML.*]$/, - /^\[object Window.*]$/ -] +/** + * Checks if an object has non-serializable properties. + * Non-serializable properties are functions and RegExp objects. + * + * @param {Object} obj - The object to check. + * @param {Set} checkedObjects - A set of properties that have already been checked. + * @returns {boolean} - Returns true if the object has non-serializable properties, false otherwise. + */ +export function hasNonSerializableProperty(obj, checkedObjects = new Set()) { + for (const key in obj) { + const value = obj[key]; + const type = typeof value; + + if ( + value === undefined || + type === 'function' || + type === 'symbol' || + value instanceof RegExp || + value instanceof Map || + value instanceof Set || + value instanceof Date || + (value !== null && type === 'object' && value.hasOwnProperty('toJSON')) + ) { + return true; + } + if (value !== null && type === 'object' && value.constructor === Object) { + if (checkedObjects.has(value)) { + // circular reference, means we have a non-serializable property + return true; + } + checkedObjects.add(value); + if (hasNonSerializableProperty(value, checkedObjects)) { + return true; + } + } + } + return false; +} + +/** + * Returns the value of a nested property in an array of objects. + * + * @param {Array} collection - Array of objects. + * @param {String} key - Key of nested property. + * @returns {any, undefined} - Value of nested property. + */ +export function setOnAny(collection, key) { + for (let i = 0, result; i < collection.length; i++) { + result = deepAccess(collection[i], key); + if (result) { + return result; + } + } + return undefined; +} -export function cloneEventArguments(args) { +export function extractDomainFromHost(pageHost) { + let domain = null; try { - return JSON.parse( - JSON.stringify( - args, - // When data has a reference to window or any html elements - // JSON.stringify fails with TypeError: Converting circular structure to JSON - // replacer function below excludes referenced html elements - (_key, value) => { - if (skipPatterns.some(pattern => pattern.test(String(value))) || typeof value === 'function') { - return undefined; - } - return value; - }, - ), - ); + let domains = /[-\w]+\.([-\w]+|[-\w]{3,}|[-\w]{1,3}\.[-\w]{2})$/i.exec(pageHost); + if (domains != null && domains.length > 0) { + domain = domains[0]; + for (let i = 1; i < domains.length; i++) { + if (domains[i].length > domain.length) { + domain = domains[i]; + } + } + } } catch (e) { - return args; + domain = null; + } + return domain; +} + +export function triggerNurlWithCpm(bid, cpm) { + if (isStr(bid.nurl) && bid.nurl !== '') { + bid.nurl = bid.nurl.replace( + /\${AUCTION_PRICE}/, + cpm + ); + triggerPixel(bid.nurl); } } diff --git a/src/utils/focusTimeout.js b/src/utils/focusTimeout.js new file mode 100644 index 00000000000..0c54bacec97 --- /dev/null +++ b/src/utils/focusTimeout.js @@ -0,0 +1,52 @@ +let outOfFocusStart = null; // enforce null otherwise it could be undefined and the callback wouldn't execute +let timeOutOfFocus = 0; +let suspendedTimeouts = []; + +function trackTimeOutOfFocus() { + if (document.hidden) { + outOfFocusStart = Date.now() + } else { + timeOutOfFocus += Date.now() - (outOfFocusStart ?? 0); // when the page is loaded in hidden state outOfFocusStart is undefined, which results in timeoutOffset being NaN + outOfFocusStart = null; + suspendedTimeouts.forEach(({ callback, startTime, setTimerId }) => setTimerId(setFocusTimeout(callback, timeOutOfFocus - startTime)())); + suspendedTimeouts = []; + } +} + +document.addEventListener('visibilitychange', trackTimeOutOfFocus); + +export function reset() { + outOfFocusStart = null; + timeOutOfFocus = 0; + suspendedTimeouts = []; + document.removeEventListener('visibilitychange', trackTimeOutOfFocus); + document.addEventListener('visibilitychange', trackTimeOutOfFocus); +} + +/** + * Wraps native setTimeout function in order to count time only when page is focused + * + * @param {function(*): ()} [callback] - A function that will be invoked after passed time + * @param {number} [milliseconds] - Minimum duration (in milliseconds) that the callback will be executed after + * @returns {function(*): (number)} - Getter function for current timer id + */ +export function setFocusTimeout(callback, milliseconds) { + const startTime = timeOutOfFocus; + let timerId = setTimeout(() => { + if (timeOutOfFocus === startTime && outOfFocusStart == null) { + callback(); + } else if (outOfFocusStart != null) { + // case when timeout ended during page is out of focus + suspendedTimeouts.push({ + callback, + startTime, + setTimerId(newId) { + timerId = newId; + } + }) + } else { + timerId = setFocusTimeout(callback, timeOutOfFocus - startTime)(); + } + }, milliseconds); + return () => timerId; +} diff --git a/src/utils/gpdr.js b/src/utils/gdpr.js similarity index 100% rename from src/utils/gpdr.js rename to src/utils/gdpr.js diff --git a/src/utils/ipUtils.js b/src/utils/ipUtils.js new file mode 100644 index 00000000000..600c0e75087 --- /dev/null +++ b/src/utils/ipUtils.js @@ -0,0 +1,52 @@ +export function scrubIPv4(ip) { + if (!ip) { + return null; + } + + const ones = 24; + + let ipParts = ip.split('.').map(Number) + + if (ipParts.length != 4) { + return null; + } + + let mask = []; + for (let i = 0; i < 4; i++) { + let n = Math.max(0, Math.min(8, ones - (i * 8))); + mask.push((0xff << (8 - n)) & 0xff); + } + + let maskedIP = ipParts.map((part, i) => part & mask[i]); + + return maskedIP.join('.'); +} + +export function scrubIPv6(ip) { + if (!ip) { + return null; + } + + const ones = 64; + + let ipParts = ip.split(':').map(part => parseInt(part, 16)); + + ipParts = ipParts.map(part => isNaN(part) ? 0 : part); + while (ipParts.length < 8) { + ipParts.push(0); + } + + if (ipParts.length != 8) { + return null; + } + + let mask = []; + for (let i = 0; i < 8; i++) { + let n = Math.max(0, Math.min(16, ones - (i * 16))); + mask.push((0xffff << (16 - n)) & 0xffff); + } + + let maskedIP = ipParts.map((part, i) => part & mask[i]); + + return maskedIP.map(part => part.toString(16)).join(':'); +} diff --git a/src/utils/perfMetrics.js b/src/utils/perfMetrics.js index b1fdb38effe..d0736b71554 100644 --- a/src/utils/perfMetrics.js +++ b/src/utils/perfMetrics.js @@ -63,9 +63,9 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the tame passed since `checkpoint`, and optionally save it as a metric. * - * @param checkpoint checkpoint name - * @param metric? metric name - * @return {number} time between now and `checkpoint` + * @param {string} checkpoint checkpoint name + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between now and the checkpoint, or `null` if the checkpoint is not found. */ function timeSince(checkpoint, metric) { const ts = getTimestamp(checkpoint); @@ -79,10 +79,10 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make /** * Get the time passed between `startCheckpoint` and `endCheckpoint`, optionally saving it as a metric. * - * @param startCheckpoint begin checkpoint - * @param endCheckpoint end checkpoint - * @param metric? metric name - * @return {number} time passed between `startCheckpoint` and `endCheckpoint` + * @param {string} startCheckpoint - The name of the starting checkpoint. + * @param {string} endCheckpoint - The name of the ending checkpoint. + * @param {string} [metric] - The name of the metric to save. Optional. + * @returns {number|null} - The time in milliseconds between `startCheckpoint` and `endCheckpoint`, or `null` if either checkpoint is not found. */ function timeBetween(startCheckpoint, endCheckpoint, metric) { const start = getTimestamp(startCheckpoint); @@ -128,12 +128,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make } /** - * @typedef {function: T} HookFn - * @property {function(T): void} bail + * @typedef {Function} HookFn + * @property {Function(T): void} bail * * @template T - * @typedef {T: HookFn} TimedHookFn - * @property {function(): void} stopTiming + * @typedef {HookFn} TimedHookFn + * @property {Function(): void} stopTiming * @property {T} untimed */ @@ -141,12 +141,12 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * Convenience method for measuring time spent in a `.before` or `.after` hook. * * @template T - * @param name metric name - * @param {HookFn} next the hook's `next` (first) argument - * @param {function(TimedHookFn): T} fn a function that will be run immediately; it takes `next`, + * @param {string} name - The metric name. + * @param {HookFn} next - The hook's `next` (first) argument. + * @param {function(TimedHookFn): T} fn - A function that will be run immediately; it takes `next`, * where both `next` and `next.bail` automatically * call `stopTiming` before continuing with the original hook. - * @return {T} fn's return value + * @return {T} - The return value of `fn`. */ function measureHookTime(name, next, fn) { const stopTiming = startTiming(name); @@ -208,10 +208,11 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * ``` * * - * @param propagate if false, the forked metrics will not be propagated here - * @param stopPropagation if true, propagation from the new metrics is stopped here - instead of - * continuing up the chain (if for example these metrics were themselves created through `.fork()`) - * @param includeGroups if true, the forked metrics will also replicate metrics that were propagated + * @param {Object} [options={}] - Options for forking the metrics. + * @param {boolean} [options.propagate=true] - If false, the forked metrics will not be propagated here. + * @param {boolean} [options.stopPropagation=false] - If true, propagation from the new metrics is stopped here, instead of + * continuing up the chain (if for example these metrics were themselves created through `.fork()`). + * @param {boolean} [options.includeGroups=false] - If true, the forked metrics will also replicate metrics that were propagated * here from elsewhere. For example: * ``` * const metrics = newMetrics(); @@ -222,6 +223,7 @@ export function metricsFactory({now = getTime, mkNode = makeNode, mkTimer = make * withoutGroups.getMetrics() // {} * withGroups.getMetrics() // {foo: ['bar']} * ``` + * @returns {Object} - The new metrics object. */ function fork({propagate = true, stopPropagation = false, includeGroups = false} = {}) { return makeMetrics(mkNode([[self, {propagate, stopPropagation, includeGroups}]]), rename); diff --git a/src/utils/ttlCollection.js b/src/utils/ttlCollection.js index 0972d175848..ffc11433d06 100644 --- a/src/utils/ttlCollection.js +++ b/src/utils/ttlCollection.js @@ -1,22 +1,30 @@ import {GreedyPromise} from './promise.js'; import {binarySearch, logError, timestamp} from '../utils.js'; +import {setFocusTimeout} from './focusTimeout.js'; /** * Create a set-like collection that automatically forgets items after a certain time. * - * @param {({}) => Number|Promise} startTime? a function taking an item added to this collection, + * @param {function(*): (number|Promise)} [startTime=timestamp] - A function taking an item added to this collection, * and returning (a promise to) a timestamp to be used as the starting time for the item * (the item will be dropped after `ttl(item)` milliseconds have elapsed since this timestamp). * Defaults to the time the item was added to the collection. - * @param {({}) => Number|void|Promise} ttl a function taking an item added to this collection, + * @param {function(*): (number|void|Promise)} [ttl=() => null] - A function taking an item added to this collection, * and returning (a promise to) the duration (in milliseconds) the item should be kept in it. * May return null to indicate that the item should be persisted indefinitely. - * @param {boolean} monotonic? set to true for better performance, but only if, given any two items A and B in this collection: + * @param {boolean} [monotonic=false] - Set to true for better performance, but only if, given any two items A and B in this collection: * if A was added before B, then: * - startTime(A) + ttl(A) <= startTime(B) + ttl(B) * - Promise.all([startTime(A), ttl(A)]) never resolves later than Promise.all([startTime(B), ttl(B)]) - * @param {number} slack? maximum duration (in milliseconds) that an item is allowed to persist + * @param {number} [slack=5000] - Maximum duration (in milliseconds) that an item is allowed to persist * once past its TTL. This is also roughly the interval between "garbage collection" sweeps. + * @returns {Object} A set-like collection with automatic TTL expiration. + * @returns {function(*): void} return.add - Add an item to the collection. + * @returns {function(): void} return.clear - Clear the collection. + * @returns {function(): Array<*>} return.toArray - Get all the items in the collection, in insertion order. + * @returns {function(): void} return.refresh - Refresh the TTL for each item in the collection. + * @returns {function(function(*)): function(): void} return.onExpiry - Register a callback to be run when an item has expired and is about to be + * removed from the collection. Returns an un-registration function */ export function ttlCollection( { @@ -39,7 +47,7 @@ export function ttlCollection( if (pendingPurge.length > 0) { const now = timestamp(); nextPurge = Math.max(now, pendingPurge[0].expiry + slack); - task = setTimeout(() => { + task = setFocusTimeout(() => { const now = timestamp(); let cnt = 0; for (const entry of pendingPurge) { diff --git a/src/video.js b/src/video.js index ff137892a2b..9be9adec4c5 100644 --- a/src/video.js +++ b/src/video.js @@ -1,4 +1,4 @@ -import {deepAccess, logError} from './utils.js'; +import {deepAccess, isArrayOfNums, isInteger, isNumber, isPlainObject, isStr, logError, logWarn} from './utils.js'; import {config} from '../src/config.js'; import {hook} from './hook.js'; import {auctionManager} from './auctionManager.js'; @@ -6,6 +6,47 @@ import {auctionManager} from './auctionManager.js'; export const OUTSTREAM = 'outstream'; export const INSTREAM = 'instream'; +/** + * List of OpenRTB 2.x video object properties with simple validators. + * Not included: `companionad`, `durfloors`, `ext` + * reference: https://github.com/InteractiveAdvertisingBureau/openrtb2.x/blob/main/2.6.md + */ +export const ORTB_VIDEO_PARAMS = new Map([ + [ 'mimes', value => Array.isArray(value) && value.length > 0 && value.every(v => typeof v === 'string') ], + [ 'minduration', isInteger ], + [ 'maxduration', isInteger ], + [ 'startdelay', isInteger ], + [ 'maxseq', isInteger ], + [ 'poddur', isInteger ], + [ 'protocols', isArrayOfNums ], + [ 'w', isInteger ], + [ 'h', isInteger ], + [ 'podid', isStr ], + [ 'podseq', isInteger ], + [ 'rqddurs', isArrayOfNums ], + [ 'placement', isInteger ], // deprecated, see plcmt + [ 'plcmt', isInteger ], + [ 'linearity', isInteger ], + [ 'skip', value => [1, 0].includes(value) ], + [ 'skipmin', isInteger ], + [ 'skipafter', isInteger ], + [ 'sequence', isInteger ], // deprecated + [ 'slotinpod', isInteger ], + [ 'mincpmpersec', isNumber ], + [ 'battr', isArrayOfNums ], + [ 'maxextended', isInteger ], + [ 'minbitrate', isInteger ], + [ 'maxbitrate', isInteger ], + [ 'boxingallowed', isInteger ], + [ 'playbackmethod', isArrayOfNums ], + [ 'playbackend', isInteger ], + [ 'delivery', isArrayOfNums ], + [ 'pos', isInteger ], + [ 'api', isArrayOfNums ], + [ 'companiontype', isArrayOfNums ], + [ 'poddedupe', isArrayOfNums ] +]); + export function fillVideoDefaults(adUnit) { const video = adUnit?.mediaTypes?.video; if (video != null && video.plcmt == null) { @@ -17,6 +58,42 @@ export function fillVideoDefaults(adUnit) { } } +/** + * validateOrtbVideoFields mutates the `adUnit.mediaTypes.video` object by removing invalid ortb properties (default). + * The onInvalidParam callback can be used to handle invalid properties differently. + * Other properties are ignored and kept as is. + * + * @param {Object} adUnit - The adUnit object. + * @param {Function} onInvalidParam - The callback function to be called with key, value, and adUnit. + * @returns {void} + */ +export function validateOrtbVideoFields(adUnit, onInvalidParam) { + const videoParams = adUnit?.mediaTypes?.video; + + if (!isPlainObject(videoParams)) { + logWarn(`validateOrtbVideoFields: videoParams must be an object.`); + return; + } + + if (videoParams != null) { + Object.entries(videoParams) + .forEach(([key, value]) => { + if (!ORTB_VIDEO_PARAMS.has(key)) { + return + } + const isValid = ORTB_VIDEO_PARAMS.get(key)(value); + if (!isValid) { + if (typeof onInvalidParam === 'function') { + onInvalidParam(key, value, adUnit); + } else { + delete videoParams[key]; + logWarn(`Invalid prop in adUnit "${adUnit.code}": Invalid value for mediaTypes.video.${key} ORTB property. The property has been removed.`); + } + } + }); + } +} + /** * @typedef {object} VideoBid * @property {string} adId id of the bid @@ -25,7 +102,8 @@ export function fillVideoDefaults(adUnit) { /** * Validate that the assets required for video context are present on the bid * @param {VideoBid} bid Video bid to validate - * @param index + * @param {Object} [options] - Options object + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index` * @return {Boolean} If object is valid */ export function isValidVideoBid(bid, {index = auctionManager.index} = {}) { diff --git a/src/videoCache.js b/src/videoCache.js index ce03f2f624e..cf39c1c9452 100644 --- a/src/videoCache.js +++ b/src/videoCache.js @@ -12,6 +12,8 @@ import {ajaxBuilder} from './ajax.js'; import {config} from './config.js'; import {auctionManager} from './auctionManager.js'; +import {logError, logWarn} from './utils.js'; +import {addBidToAuction} from './auction.js'; /** * Might be useful to be configurable in the future @@ -39,7 +41,7 @@ const ttlBufferInSeconds = 15; * Function which wraps a URI that serves VAST XML, so that it can be loaded. * * @param {string} uri The URI where the VAST content can be found. - * @param {string} impUrl An impression tracker URL for the delivery of the video ad + * @param {(string|string[])} impTrackerURLs An impression tracker URL for the delivery of the video ad * @return A VAST URL which loads XML from the given URI. */ function wrapURI(uri, impTrackerURLs) { @@ -65,7 +67,9 @@ function wrapURI(uri, impTrackerURLs) { * the bid can't be converted cleanly. * * @param {CacheableBid} bid - * @param index + * @param {Object} [options] - Options object. + * @param {Object} [options.index=auctionManager.index] - Index object, defaulting to `auctionManager.index`. + * @return {Object|null} - The payload to be sent to the prebid-server endpoints, or null if the bid can't be converted cleanly. */ function toStorageRequest(bid, {index = auctionManager.index} = {}) { const vastValue = bid.vastXml ? bid.vastXml : wrapURI(bid.vastUrl, bid.vastImpUrl); @@ -157,3 +161,73 @@ export function store(bids, done, getAjax = ajaxBuilder) { export function getCacheUrl(id) { return `${config.getConfig('cache.url')}?uuid=${id}`; } + +export const _internal = { + store +} + +export function storeBatch(batch) { + const bids = batch.map(entry => entry.bidResponse) + function err(msg) { + logError(`Failed to save to the video cache: ${msg}. Video bids will be discarded:`, bids) + } + _internal.store(bids, function (error, cacheIds) { + if (error) { + err(error) + } else if (batch.length !== cacheIds.length) { + logError(`expected ${batch.length} cache IDs, got ${cacheIds.length} instead`) + } else { + cacheIds.forEach((cacheId, i) => { + const {auctionInstance, bidResponse, afterBidAdded} = batch[i]; + if (cacheId.uuid === '') { + logWarn(`Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded.`); + } else { + bidResponse.videoCacheKey = cacheId.uuid; + if (!bidResponse.vastUrl) { + bidResponse.vastUrl = getCacheUrl(bidResponse.videoCacheKey); + } + addBidToAuction(auctionInstance, bidResponse); + afterBidAdded(); + } + }); + } + }); +}; + +let batchSize, batchTimeout; +if (FEATURES.VIDEO) { + config.getConfig('cache', (cacheConfig) => { + batchSize = typeof cacheConfig.cache.batchSize === 'number' && cacheConfig.cache.batchSize > 0 + ? cacheConfig.cache.batchSize + : 1; + batchTimeout = typeof cacheConfig.cache.batchTimeout === 'number' && cacheConfig.cache.batchTimeout > 0 + ? cacheConfig.cache.batchTimeout + : 0; + }); +} + +export const batchingCache = (timeout = setTimeout, cache = storeBatch) => { + let batches = [[]]; + let debouncing = false; + const noTimeout = cb => cb(); + + return function (auctionInstance, bidResponse, afterBidAdded) { + const batchFunc = batchTimeout > 0 ? timeout : noTimeout; + if (batches[batches.length - 1].length >= batchSize) { + batches.push([]); + } + + batches[batches.length - 1].push({auctionInstance, bidResponse, afterBidAdded}); + + if (!debouncing) { + debouncing = true; + batchFunc(() => { + batches.forEach(cache); + batches = [[]]; + debouncing = false; + }, batchTimeout); + } + }; +}; + +export const batchAndStore = batchingCache(); diff --git a/test/fixtures/fixtures.js b/test/fixtures/fixtures.js index fb6cfe036e5..94513821ce1 100644 --- a/test/fixtures/fixtures.js +++ b/test/fixtures/fixtures.js @@ -966,7 +966,7 @@ export function getBidResponsesFromAPI() { export function getAdServerTargeting() { return { '/19968336/header-bid-tag-0': convertTargetingsFromOldToNew({ - 'foobar': '0x0,300x250,300x600', + 'foobar': '300x250,300x600,0x0', 'hb_size': '300x250', 'hb_pb': '10.00', 'hb_adid': '233bcbee889d46d', @@ -1035,7 +1035,7 @@ export function getTargetingKeys() { ], [ 'foobar', - ['0x0', '300x250', '300x600'] + ['300x250', '300x600', '0x0'] ] ]; } @@ -1062,7 +1062,7 @@ export function getTargetingKeysBidLandscape() { ], [ 'foobar', - ['0x0', '300x250', '300x600'] + ['300x250', '300x600', '0x0'] ], [ TARGETING_KEYS.BIDDER + '_triplelift', diff --git a/test/helpers/cookies.js b/test/helpers/cookies.js new file mode 100644 index 00000000000..a0d9da3c595 --- /dev/null +++ b/test/helpers/cookies.js @@ -0,0 +1,5 @@ +export function clearAllCookies() { + document.cookie.split(';').forEach(function (c) { + document.cookie = c.replace(/^ +/, '').replace(/=.*/, '=;expires=' + new Date().toUTCString() + ';path=/'); + }); +} diff --git a/test/helpers/index_adapter_utils.js b/test/helpers/index_adapter_utils.js index f01145b573d..0eb7af88d14 100644 --- a/test/helpers/index_adapter_utils.js +++ b/test/helpers/index_adapter_utils.js @@ -1,3 +1,5 @@ +import { deepClone } from '../../src/utils'; + var AllowedAdUnits = [[728, 90], [120, 600], [300, 250], [160, 600], [336, 280], [234, 60], [300, 600], [300, 50], [320, 50], [970, 250], [300, 1050], [970, 90], [180, 150]]; var UnsupportedAdUnits = [[700, 100], [100, 600], [300, 200], [100, 600], [300, 200], [200, 60], [900, 200], [300, 1000], [900, 90], [100, 100]]; @@ -117,7 +119,7 @@ exports.getExpectedIndexSlots = function(bids) { } function clone(x) { - return JSON.parse(JSON.stringify(x)); + return deepClone(x); } // returns the difference(lhs, rhs), difference(rhs,lhs), and intersection(lhs, rhs) based on the object keys diff --git a/test/helpers/karma-init.js b/test/helpers/karma-init.js deleted file mode 100644 index 56e936aa741..00000000000 --- a/test/helpers/karma-init.js +++ /dev/null @@ -1,6 +0,0 @@ -(function (window) { - if (!window.parent.pbjsKarmaInitDone && window.location.pathname === '/context.html') { - window.parent.pbjsKarmaInitDone = true; - window.open('/debug.html', '_blank'); - } -})(window); diff --git a/test/mocks/adloaderStub.js b/test/mocks/adloaderStub.js index 6e7f5756100..24f5781dfc4 100644 --- a/test/mocks/adloaderStub.js +++ b/test/mocks/adloaderStub.js @@ -8,9 +8,7 @@ export let loadExternalScriptStub = createStub(); function createStub() { return sinon.stub(adloader, 'loadExternalScript').callsFake((...args) => { - if (typeof args[2] === 'function') { - args[2](); - } else if (typeof args[3] === 'function') { + if (typeof args[3] === 'function') { args[3](); } return document.createElement('script'); diff --git a/test/mocks/ortbConverter.js b/test/mocks/ortbConverter.js new file mode 100644 index 00000000000..446fac4629a --- /dev/null +++ b/test/mocks/ortbConverter.js @@ -0,0 +1,8 @@ +import {defaultProcessors} from '../../libraries/ortbConverter/converter.js'; +import {pbsExtensions} from '../../libraries/pbsExtensions/pbsExtensions.js'; + +beforeEach(() => { + // disable caching of default processors so that tests do not freeze a subset for other tests + defaultProcessors.clear(); + pbsExtensions.clear(); +}); diff --git a/test/mocks/videoCacheStub.js b/test/mocks/videoCacheStub.js index 7ce899cae35..acae5cd6a32 100644 --- a/test/mocks/videoCacheStub.js +++ b/test/mocks/videoCacheStub.js @@ -1,4 +1,4 @@ -import * as videoCache from 'src/videoCache.js'; +import {_internal as videoCache} from 'src/videoCache.js'; /** * Function which can be called from unit tests to stub out the video cache. diff --git a/test/pages/banner_sync.html b/test/pages/banner_sync.html new file mode 100644 index 00000000000..71403ba5570 --- /dev/null +++ b/test/pages/banner_sync.html @@ -0,0 +1,97 @@ + + + + + + + Prebid.js Banner Example + + + + + + + + + + + + + + + +

Prebid.js Banner Ad Unit Test

+
+ +
+
+ + + diff --git a/test/pages/consent_mgt_gdpr.html b/test/pages/consent_mgt_gdpr.html index c55a2b9236f..6ff24938bf3 100644 --- a/test/pages/consent_mgt_gdpr.html +++ b/test/pages/consent_mgt_gdpr.html @@ -1,6 +1,6 @@ - + ", + 'cpm': 0.68, + 'nurl': nurl, + 'creativeId': 'test', + 'currency': 'USD', + 'dealId': '', + 'meta': { + 'advertiserDomains': [], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'redtram' + } + ] + } + }, + 'netRevenue': true, + 'ttl': 120, + 'metrics': {}, + 'adapterCode': 'redtram', + 'originalCpm': 0.68, + 'originalCurrency': 'USD', + 'responseTimestamp': 1668162732297, + 'requestTimestamp': 1668162732292, + 'bidder': 'redtram', + 'adUnitCode': 'div-prebid', + 'timeToRespond': 5, + 'pbLg': '0.50', + 'pbMg': '0.60', + 'pbHg': '0.68', + 'pbAg': '0.65', + 'pbDg': '0.68', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'redtram', + 'hb_adid': '5691dd18ba6ab6', + 'hb_pb': '0.68', + 'hb_size': '300x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': '' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 23611 + } + ] + }; + spec.onBidWon(bid); + expect(bid.nurl).to.deep.equal('nurl/?ap=0.68'); + }); + }); +}); diff --git a/test/spec/libraries/precisoUtils/bidUtils_spec.js b/test/spec/libraries/precisoUtils/bidUtils_spec.js new file mode 100644 index 00000000000..05c480f41bc --- /dev/null +++ b/test/spec/libraries/precisoUtils/bidUtils_spec.js @@ -0,0 +1,150 @@ + +import { expect } from 'chai'; +import { buildRequests, interpretResponse } from '../../../../libraries/precisoUtils/bidUtils.js'; + +const DEFAULT_PRICE = 1 +const DEFAULT_CURRENCY = 'USD' +const DEFAULT_BANNER_WIDTH = 300 +const DEFAULT_BANNER_HEIGHT = 250 +const BIDDER_CODE = 'preciso'; +const TESTDOMAIN = 'test.org' +const bidEndPoint = `https://${TESTDOMAIN}/bid_request/openrtb`; + +describe('bidUtils', function () { + let bid = { + bidId: '23fhj33i987f', + bidder: BIDDER_CODE, + buyerUid: 'testuid', + mediaTypes: { + banner: { + sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] + } + }, + params: { + host: 'prebid', + sourceid: '0', + publisherId: '0', + mediaType: 'banner', + region: 'IND' + + }, + userId: { + pubcid: '12355454test' + + }, + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + } + + }; + + const spec = { + // isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(bidEndPoint), + interpretResponse, + // buildUserSyncs: buildUserSyncs(syncEndPoint) + }; + + 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://${TESTDOMAIN}/bid_request/openrtb`); + }); + it('Returns valid data if array of bids is valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data.device).to.be.a('object'); + expect(data.user).to.be.a('object'); + expect(data.source).to.be.a('object'); + expect(data.site).to.be.a('object'); + }); + it('Returns data.device is undefined if no valid device object is passed', function () { + delete bid.ortb2.device; + serverRequest = spec.buildRequests([bid]); + let data = serverRequest.data; + expect(data.device).to.be.undefined; + }); + }); + + describe('interpretResponse', function () { + it('should get correct bid response', function () { + let response = { + bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ + { + bid: [ + { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: 'hi', + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + ], + seat: BIDDER_CODE + } + ], + } + let expectedResponse = [ + { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + cpm: DEFAULT_PRICE, + width: DEFAULT_BANNER_WIDTH, + height: DEFAULT_BANNER_HEIGHT, + creativeId: 'test_banner_crid', + ad: 'hi', + currency: DEFAULT_CURRENCY, + netRevenue: true, + ttl: 300, + meta: { advertiserDomains: [] }, + } + ] + let result = spec.interpretResponse({ body: response }) + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])) + }) + }); + // describe('getUserSyncs', function () { + // const syncUrl = `https://${TESTDOMAIN}/rtb/user/usersync.aspx?/iframe?pbjs=1&coppa=0`; + // const syncOptions = { + // iframeEnabled: true + // }; + // let userSync = spec.buildUserSyncs(syncOptions); + // 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).to.deep.equal([ + // { type: 'iframe', url: syncUrl } + // ]); + // }); + // }); +}); diff --git a/test/spec/libraries/processResponse_spec.js b/test/spec/libraries/processResponse_spec.js new file mode 100644 index 00000000000..8f682bd87f5 --- /dev/null +++ b/test/spec/libraries/processResponse_spec.js @@ -0,0 +1,60 @@ +import { getBidFromResponse } from '../../../libraries/processResponse/index.js'; +import {expect} from 'chai/index.js'; + +describe('processResponse', function () { + const respItem = { + 'bid': [ + { + 'price': 0.504, + 'ext': { + 'visx': { + 'events': { + 'runtime': '//t.visx.net/track/status/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/{STATUS_CODE}' + } + }, + 'prebid': { + 'events': { + 'pending': '//t.visx.net/track/pending/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'win': '//t.visx.net/track/win/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'bid_timeout': '//t.visx.net/track/bid_timeout/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/', + 'runtime': '//t.visx.net/track/status/RFTFjZflStSUyuXuyT2IKOZMVPUIiPkzebpPWYwKvNkE_IybYfFxk2P5feBnt9LhiR7291KTG11JjrnyHyhVKfolH_VRCmGppbnHXHfHJ9AgNqjhFB_yTg3m18wGO9k4LOddGAg3mk8qc5zYEIzNsPFnZzos1EkHh5WNs0EjrBpwCgTERUqM3PJD_Zy60nMDA-LCuq-Z4JNBGC_GHx4LwvwXipQsjdGHS-HkqHHf9sES45OlRrW4wMf69dsmey1gvwqFAhJwii2lzo9wfOohLCMRa3Vxd-zvzx-uw71maWOyKnJXWiP6c5xkyrfV4gukNYaDUgrHc0mA0yhqyiHxe8KzEl32rxQXJRCg4FoJcJ1g9jmpZQBnIh2QrKm5iC159elwzwf31_v3Uw97Zpek8j0CCLa8FjxSjvXm1Mq8x4jcwlt0ngfWU6WwyyKwX_GMbKWuAL_nrfxSvs1hZCb4eunEFyXb2lN2olWo8ezMEzZ8YRxF_mx0hDB3NXyV0Tb4b6KXQq7tvxV-1rKPRt7DySRTbLPht0hO3mjTHxutfihnuL6ROEr372gSAiDodnbdCq_lPsCsUSEpG7DmN-4In10uSp2MemjfbqI6tllOCO-j6Pm9mhdl_rT4anHmRG2DG_dLsfD7pLaAsgf2zl2bpawhxxLVjTxikoWjNKAvr_GNh4adHGj5EHbqaBaHovB573Yk-koHkyBNrebeiy-1-Knc28MWOpFi9XKjNsXx756jAXLx2H098ptaXF3mFiuT2Iv6sTVjqOI/{STATUS_CODE}' + }, + 'meta': { + 'mediaType': 'banner' + } + } + }, + 'impid': '2b642c27bdcf8f', + 'auid': 929004, + 'h': 250, + 'cur': 'EUR', + 'adomain': [ + '' + ], + 'w': 300, + 'id': '9b6c7e04-0a09-4add-8ba9-0c8b98304de3' + } + ], + 'seat': '1429601' + }; + 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', + 'emptyResponse': 'Response is empty', + '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 - ', + 'onlyVideoInstream': 'Only video instream supported', + 'videoMissing': 'Bid request videoType property is missing - ' + }; + it('returns bid when respItem and LOG_ERROR_MESS is passed', function () { + let response = getBidFromResponse(respItem, LOG_ERROR_MESS); + expect(response).not.include.any.keys('emptyResponse', 'hasNoArrayOfBids', 'emptySeatbid'); + }); +}); diff --git a/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js new file mode 100644 index 00000000000..ba8b986163b --- /dev/null +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -0,0 +1,541 @@ +import { expect } from 'chai'; +import { isBidRequestValid, buildRequests, interpretResponse, getUserSyncs } from '../../../../libraries/teqblazeUtils/bidderUtils.js'; +import { BANNER, VIDEO, NATIVE } from '../../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../../src/utils.js'; + +const bidder = 'bidder'; +const DOMAIN = 'test.org'; +const AD_URL = `https://${DOMAIN}/pbjs`; +const SYNC_URL = `https://${DOMAIN}`; + +describe('TeqBlazeBidderUtils', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + const spec = { + isBidRequestValid: isBidRequestValid(), + buildRequests: buildRequests(AD_URL), + interpretResponse, + getUserSyncs: getUserSyncs(SYNC_URL) + }; + + 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 general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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('Handles ORTB2 device data', function () { + const ortb2Device = { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }; + const _bidderRequest = JSON.parse(JSON.stringify(bidderRequest)); + _bidderRequest.ortb2.device = ortb2Device; + const _request = spec.buildRequests(bids, _bidderRequest); + + expect(_request.data.device).to.deep.equal(ortb2Device); + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://${DOMAIN}/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://${DOMAIN}/image?pbjs=1&ccpa_consent=1---&coppa=0`) + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://${DOMAIN}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); + }); +}); diff --git a/test/spec/libraries/userAgentUtils_spec.js b/test/spec/libraries/userAgentUtils_spec.js new file mode 100644 index 00000000000..0a72b51588b --- /dev/null +++ b/test/spec/libraries/userAgentUtils_spec.js @@ -0,0 +1,109 @@ +/* globals describe, beforeEach, afterEach, sinon */ +import { expect } from 'chai'; +import { getDeviceType, getBrowser, getOS } from 'libraries/userAgentUtils'; +import { deviceTypes, browserTypes, osTypes } from 'libraries/userAgentUtils/userAgentTypes.enums'; + +const ORIGINAL_USER_AGENT = window.navigator.userAgent; +const ORIGINAL_VENDOR = window.navigator.vendor; +const ORIGINAL_APP_VERSION = window.navigator.appVersion; + +describe('Test user agent categorization', () => { + afterEach(() => { + window.navigator.__defineGetter__('userAgent', () => ORIGINAL_USER_AGENT); + window.navigator.__defineGetter__('vendor', () => ORIGINAL_VENDOR); + window.navigator.__defineGetter__('appVersion', () => ORIGINAL_APP_VERSION); + }) + + describe('test getDeviceType', () => { + it('user agent device type is tablet', () => { + const tabletUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4.17.9 (KHTML, like Gecko) Version/5.1 Mobile/9B206 Safari/7534.48.3' + window.navigator.__defineGetter__('userAgent', () => tabletUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.TABLET); + }) + it('user agent device type is mobile', () => { + const mobileUserAgent = 'Mozilla/5.0 (Linux; Android 12; M2102J20SG) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Mobile Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => mobileUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.MOBILE); + }) + it('user agent device type is desktop', () => { + const desktopUserAgent = 'Mozilla/5.0 (X11; CrOS x86_64 8172.45.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2704.64 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => desktopUserAgent); + expect(getDeviceType()).to.equal(deviceTypes.DESKTOP); + }) + }) + + describe('test getBrowser', () => { + it('user agent browser is edge', () => { + const edgeUserAgent = '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.10532' + window.navigator.__defineGetter__('userAgent', () => edgeUserAgent); + expect(getBrowser()).to.equal(browserTypes.EDGE); + }) + it('user agent browser is chrome', () => { + const chromeUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) CriOS/44.0.2403.67 Mobile/12H143 Safari/600.1.4' + window.navigator.__defineGetter__('userAgent', () => chromeUserAgent); + expect(getBrowser()).to.equal(browserTypes.CHROME); + }) + it('user agent browser is firefox', () => { + const firefoxUserAgent = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:40.0) Gecko/20100101 Firefox/40.0.2 Waterfox/40.0.2' + window.navigator.__defineGetter__('userAgent', () => firefoxUserAgent); + expect(getBrowser()).to.equal(browserTypes.FIREFOX); + }) + it('user agent browser is safari', () => { + const safariUserAgent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/37.0.2062.124 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => safariUserAgent); + window.navigator.__defineGetter__('vendor', () => 'Apple Computer, Inc.'); + expect(getBrowser()).to.equal(browserTypes.SAFARI); + }) + it('user agent browser is internet explorer', () => { + const internetexplorerUserAgent = 'Mozilla/5.0 (MSIE 9.0; Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko' + window.navigator.__defineGetter__('userAgent', () => internetexplorerUserAgent); + expect(getBrowser()).to.equal(browserTypes.INTERNET_EXPLORER); + }) + it('user agent is other', () => { + const otherUserAgent = 'Dalvik/2.1.0 (Linux; U; Android 9; ADT-2 Build/PTT5.181126.002)' + window.navigator.__defineGetter__('userAgent', () => otherUserAgent); + expect(getBrowser()).to.equal(browserTypes.OTHER); + }) + }) + + describe('test getOS', () => { + it('user agent is android', () => { + const androidUserAgent = 'Mozilla/5.0 (Android; Mobile; rv:40.0) Gecko/40.0 Firefox/40.0' + window.navigator.__defineGetter__('userAgent', () => androidUserAgent); + expect(getOS()).to.equal(osTypes.ANDROID); + }) + it('user agent is ios', () => { + const iosUserAgent = 'Mozilla/5.0 (iPad; CPU OS 8_4_1 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Version/8.0 Mobile/12H321 Safari/600.1.4' + window.navigator.__defineGetter__('userAgent', () => iosUserAgent); + expect(getOS()).to.equal(osTypes.IOS); + }) + it('user agent is windows', () => { + const windowsUserAgent = 'Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => windowsUserAgent); + expect(getOS()).to.equal(osTypes.WINDOWS); + }) + it('user agent is mac', () => { + const macUserAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.10; rv:33.0) Gecko/20100101 Firefox/33.0' + window.navigator.__defineGetter__('userAgent', () => macUserAgent); + expect(getOS()).to.equal(osTypes.MAC); + }) + it('user agent is linux', () => { + const linuxUserAgent = 'Mozilla/5.0 (X11; Linux x86_64; rv:31.0) Gecko/20100101 Firefox/31.0' + window.navigator.__defineGetter__('userAgent', () => linuxUserAgent); + expect(getOS()).to.equal(osTypes.LINUX); + }) + it('user agent is unix', () => { + const unixUserAgent = 'Mozilla/5.0 (X11; CrOS armv7l 7077.134.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.156 Safari/537.36' + const unixappVersion = '5.0 (X11; CrOS armv7l 7077.134.0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.156 Safari/537.36' + window.navigator.__defineGetter__('userAgent', () => unixUserAgent); + window.navigator.__defineGetter__('appVersion', () => unixappVersion); + expect(getOS()).to.equal(osTypes.UNIX); + }) + it('user agent is other', () => { + const otherUserAgent = 'Mozilla/5.0 (compatible; Googlebot/2.1; +http://www.google.com/bot.html)' + window.navigator.__defineGetter__('userAgent', () => otherUserAgent); + window.navigator.__defineGetter__('appVersion', () => ''); + expect(getOS()).to.equal(osTypes.OTHER); + }) + }) +}) diff --git a/test/spec/libraries/vastTrackers_spec.js b/test/spec/libraries/vastTrackers_spec.js index 3849ea75b02..3e8a8456e9c 100644 --- a/test/spec/libraries/vastTrackers_spec.js +++ b/test/spec/libraries/vastTrackers_spec.js @@ -1,33 +1,88 @@ -import {addImpUrlToTrackers, getVastTrackers, insertVastTrackers, registerVastTrackers} from 'libraries/vastTrackers/vastTrackers.js'; +import { + addImpUrlToTrackers, + addTrackersToResponse, + getVastTrackers, + insertVastTrackers, + registerVastTrackers, + reset, responseHook, + disable +} from 'libraries/vastTrackers/vastTrackers.js'; import {MODULE_TYPE_ANALYTICS} from '../../../src/activities/modules.js'; +import {AuctionIndex} from '../../../src/auctionIndex.js'; describe('vast trackers', () => { - it('insert into tracker list', function() { - let trackers = getVastTrackers({'cpm': 1.0}); - if (!trackers || !trackers.get('impressions')) { - registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', function(bidResponse) { - return [ - {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} - ]; - }); + let sandbox, tracker, auction, bid, bidRequest, index; + beforeEach(() => { + bid = { + requestId: 'bid', + cpm: 1.0, + auctionId: 'aid', + mediaType: 'video', } - trackers = getVastTrackers({'cpm': 1.0}); + bidRequest = { + auctionId: 'aid', + bidId: 'bid', + } + auction = { + getAuctionId() { + return 'aid'; + }, + getProperties() { + return {auction: 'props'}; + }, + getBidRequests() { + return [{bids: [bidRequest]}] + } + }; + sandbox = sinon.sandbox.create(); + index = new AuctionIndex(() => [auction]); + tracker = sinon.stub().callsFake(function (bidResponse) { + return [ + {'event': 'impressions', 'url': `https://vasttracking.mydomain.com/vast?cpm=${bidResponse.cpm}`} + ]; + }); + registerVastTrackers(MODULE_TYPE_ANALYTICS, 'test', tracker); + }) + afterEach(() => { + reset(); + sandbox.restore(); + }); + + after(disable); + + it('insert into tracker list', function () { + const trackers = getVastTrackers(bid, {index}); expect(trackers).to.be.a('map'); expect(trackers.get('impressions')).to.exists; expect(trackers.get('impressions').has('https://vasttracking.mydomain.com/vast?cpm=1')).to.be.true; }); - it('insert trackers in vastXml', function() { - const trackers = getVastTrackers({'cpm': 1.0}); + it('insert trackers in vastXml', function () { + const trackers = getVastTrackers(bid, {index}); let vastXml = ''; vastXml = insertVastTrackers(trackers, vastXml); expect(vastXml).to.equal(''); }); - it('test addImpUrlToTrackers', function() { - const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers({'cpm': 1.0})); + it('should pass request and auction properties to trackerFn', () => { + const bid = {requestId: 'bid', auctionId: 'aid'}; + getVastTrackers(bid, {index}); + sinon.assert.calledWith(tracker, bid, sinon.match({auction: auction.getProperties(), bidRequest})) + }) + + it('test addImpUrlToTrackers', function () { + const trackers = addImpUrlToTrackers({'vastImpUrl': 'imptracker.com'}, getVastTrackers(bid, {index})); expect(trackers).to.be.a('map'); expect(trackers.get('impressions')).to.exists; expect(trackers.get('impressions').has('imptracker.com')).to.be.true; }); + + if (FEATURES.VIDEO) { + it('should add trackers to bid response', () => { + responseHook({index})(sinon.stub(), 'au', bid); + expect(bid.vastImpUrl).to.eql([ + 'https://vasttracking.mydomain.com/vast?cpm=1' + ]) + }); + } }) diff --git a/test/spec/libraries/weakStore_spec.js b/test/spec/libraries/weakStore_spec.js new file mode 100644 index 00000000000..407b83391ef --- /dev/null +++ b/test/spec/libraries/weakStore_spec.js @@ -0,0 +1,32 @@ +import {weakStore} from '../../../libraries/weakStore/weakStore.js'; + +describe('weakStore', () => { + let targets, store; + beforeEach(() => { + targets = { + id: {} + }; + store = weakStore((id) => targets[id]); + }); + + it('returns undef if getter returns undef', () => { + expect(store('missing')).to.not.exist; + }); + + it('inits to empty object by default', () => { + expect(store('id')).to.eql({}); + }); + + it('inits to given value', () => { + expect(store('id', {initial: 'value'})).to.eql({'initial': 'value'}); + }); + + it('returns the same object as long as the target does not change', () => { + expect(store('id')).to.equal(store('id')); + }); + + it('ignores init value if already initialized', () => { + store('id', {initial: 'value'}); + expect(store('id', {second: 'value'})).to.eql({initial: 'value'}); + }) +}); diff --git a/test/spec/modules/1plusXRtdProvider_spec.js b/test/spec/modules/1plusXRtdProvider_spec.js index 4e4092ea26e..1059f43fb4d 100644 --- a/test/spec/modules/1plusXRtdProvider_spec.js +++ b/test/spec/modules/1plusXRtdProvider_spec.js @@ -12,6 +12,7 @@ import { updateBidderConfig, } from 'modules/1plusXRtdProvider'; import {deepClone} from '../../../src/utils.js'; +import { STORAGE_TYPE_COOKIES, STORAGE_TYPE_LOCALSTORAGE } from 'src/storageManager.js'; describe('1plusXRtdProvider', () => { // Fake server config @@ -126,6 +127,7 @@ describe('1plusXRtdProvider', () => { const customerId = 'test'; const timeout = 1000; const bidders = ['appnexus']; + const fpidStorageType = STORAGE_TYPE_LOCALSTORAGE it('Throws an error if no customerId is specified', () => { const moduleConfig = { params: { timeout, bidders } }; @@ -141,13 +143,14 @@ describe('1plusXRtdProvider', () => { expect(() => extractConfig(moduleConfig, reqBidsConfigEmpty)).to.throw(); }) it('Returns an object containing the parameters specified', () => { - const moduleConfig = { params: { customerId, timeout, bidders } }; - const expectedKeys = ['customerId', 'timeout', 'bidders'] + const moduleConfig = { params: { customerId, timeout, bidders, fpidStorageType } }; + const expectedKeys = ['customerId', 'timeout', 'bidders', 'fpidStorageType'] const extractedConfig = extractConfig(moduleConfig, reqBidsConfigObj); expect(extractedConfig).to.be.an('object').and.to.have.all.keys(expectedKeys); expect(extractedConfig.customerId).to.equal(customerId); expect(extractedConfig.timeout).to.equal(timeout); expect(extractedConfig.bidders).to.deep.equal(bidders); + expect(extractedConfig.fpidStorageType).to.equal(fpidStorageType) }) /* 1plusX RTD module may only use bidders that are both specified in : - the bid request configuration @@ -166,6 +169,20 @@ describe('1plusXRtdProvider', () => { const moduleConfig = { params: { customerId, timeout, bidders } }; expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); }) + it('Throws an error if wrong fpidStorageType is provided', () => { + const moduleConfig = { params: { customerId, timeout, bidders, fpidStorageType: 'bogus' } }; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj).to.throw()) + }) + it('Defaults fpidStorageType to localStorage', () => { + const moduleConfig = { params: { customerId, timeout, bidders } }; + const extractedConfig = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedConfig.fpidStorageType).to.equal(STORAGE_TYPE_LOCALSTORAGE) + }) + it('Correctly instantiates fpidStorageType to cookie store if instructed', () => { + const moduleConfig = { params: { customerId, timeout, bidders, fpidStorageType: STORAGE_TYPE_COOKIES } }; + const extractedConfig = extractConfig(moduleConfig, reqBidsConfigObj); + expect(extractedConfig.fpidStorageType).to.equal(STORAGE_TYPE_COOKIES) + }) }) describe('buildOrtb2Updates', () => { @@ -284,17 +301,6 @@ describe('1plusXRtdProvider', () => { }) }) - describe('extractFpid', () => { - it('correctly extracts an ope fpid if present', () => { - window.localStorage.setItem('ope_fpid', 'oneplusx_test_key') - const id1 = extractFpid() - window.localStorage.removeItem('ope_fpid') - const id2 = extractFpid() - expect(id1).to.equal('oneplusx_test_key') - expect(id2).to.equal(null) - }) - }) - describe('getPapiUrl', () => { const customer = 'acme' const consent = { diff --git a/test/spec/modules/33acrossBidAdapter_spec.js b/test/spec/modules/33acrossBidAdapter_spec.js index 9cc038428bc..ff05f412d58 100644 --- a/test/spec/modules/33acrossBidAdapter_spec.js +++ b/test/spec/modules/33acrossBidAdapter_spec.js @@ -121,7 +121,7 @@ describe('33acrossBidAdapter:', function () { video: { w: 300, h: 250, - placement: 2, + plcmt: 2, ...params } }); @@ -527,27 +527,6 @@ describe('33acrossBidAdapter:', function () { }); }); - it('returns false for invalid bidder name values', function() { - const invalidBidderName = [ - undefined, - '33', - '33x', - 'thirtythree', - '' - ]; - - invalidBidderName.forEach((bidderName) => { - const bid = { - bidder: bidderName, - params: { - siteId: 'sample33xGUID123456789' - } - }; - - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - }); - 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 @@ -733,6 +712,11 @@ describe('33acrossBidAdapter:', function () { 'foo' ]; + invalidPlacement.forEach((placement) => { + this.bid.mediaTypes.video.plcmt = placement; + expect(spec.isBidRequestValid(this.bid)).to.be.false; + }); + invalidPlacement.forEach((placement) => { this.bid.mediaTypes.video.placement = placement; expect(spec.isBidRequestValid(this.bid)).to.be.false; @@ -1520,89 +1504,141 @@ describe('33acrossBidAdapter:', function () { }); }); - 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() - ); + context('when mediaType has video only', function() { + context('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(); + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); - ttxRequest.imp[0].video.placement = 1; - ttxRequest.imp[0].video.startdelay = 0; + ttxRequest.imp[0].video.startdelay = 0; - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); - }); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); - it('builds instream request with params passed', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'instream', startdelay: -2}) - .build() - ); + 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 ttxRequest = new TtxRequestBuilder() + .withVideo({startdelay: -2}) + .withProduct('instream') + .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + expect(JSON.parse(builtServerRequest.data)).to.deep.equal(ttxRequest); + }); + + context('when the placement is still specified in the DEPRECATED `placement` field', function() { + it('does not overwrite its value and does not set it in the recent `plcmt` field as well', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({ + placement: 2, // Incorrect placement for an instream video + context: 'instream' + }) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('instream') + .build(); + + ttxRequest.imp[0].video.placement = 2; + ttxRequest.imp[0].video.startdelay = 0; + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + 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() - ); + context('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(); + const ttxRequest = new TtxRequestBuilder() + .withVideo() + .withProduct('siab') + .build(); - ttxRequest.imp[0].video.placement = 2; + // No placement specified, final value should default to 2. + ttxRequest.imp[0].video.plcmt = 2; - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); - }); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); - it('builds siab request with video params passed', function() { - const bidRequests = ( - new BidRequestsBuilder() - .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) - .build() - ); + it('builds siab request with video params passed', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', plcmt: 3, playbackmethod: [2]}) + .build() + ); - const ttxRequest = new TtxRequestBuilder() - .withVideo({placement: 3, playbackmethod: [2]}) - .withProduct('siab') - .build(); + const ttxRequest = new TtxRequestBuilder() + .withVideo({plcmt: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); - const serverRequest = new ServerRequestBuilder() - .withData(ttxRequest) - .build(); - const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); - validateBuiltServerRequest(builtServerRequest, serverRequest); + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + + context('and the placement is specified in the DEPRECATED `placement` field', function() { + it('sets the recent `plcmt` field', function() { + const bidRequests = ( + new BidRequestsBuilder() + .withVideo({context: 'outstream', placement: 3, playbackmethod: [2]}) + .build() + ); + + const ttxRequest = new TtxRequestBuilder() + .withVideo({plcmt: 3, placement: 3, playbackmethod: [2]}) + .withProduct('siab') + .build(); + + const serverRequest = new ServerRequestBuilder() + .withData(ttxRequest) + .build(); + const [ builtServerRequest ] = spec.buildRequests(bidRequests, bidderRequest); + + validateBuiltServerRequest(builtServerRequest, serverRequest); + }); + }); }); }); @@ -1686,7 +1722,7 @@ describe('33acrossBidAdapter:', function () { .withProduct('siab') .build(); - ttxRequest.imp[0].video.placement = 2; + ttxRequest.imp[0].video.plcmt = 2; const serverRequest = new ServerRequestBuilder() .withData(ttxRequest) diff --git a/test/spec/modules/33acrossIdSystem_spec.js b/test/spec/modules/33acrossIdSystem_spec.js index 364f7d2845c..6ec3554d353 100644 --- a/test/spec/modules/33acrossIdSystem_spec.js +++ b/test/spec/modules/33acrossIdSystem_spec.js @@ -1,8 +1,11 @@ -import { thirthyThreeAcrossIdSubmodule, storage } from 'modules/33acrossIdSystem.js'; +import { thirthyThreeAcrossIdSubmodule, storage, domainUtils } from 'modules/33acrossIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import { uspDataHandler, coppaDataHandler, gppDataHandler } from 'src/adapterManager.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; +import {attachIdSystem} from '../../../modules/userId/index.js'; describe('33acrossIdSystem', () => { describe('name', () => { @@ -50,97 +53,344 @@ describe('33acrossIdSystem', () => { expect(completeCallback.calledOnceWithExactly('foo')).to.be.true; }); - context('if the use of a first-party ID has been enabled', () => { - context('and the response includes a first-party ID', () => { - context('and the storage type is "cookie"', () => { - it('should store the provided first-party ID in a cookie', () => { + const additionalOptions = { + 'by an option': { storeFpid: true, storeTpid: true }, + 'by default': { } // No storeFpid, default value should be true + }; + + Object.entries(additionalOptions).forEach(([caseTitle, opts]) => { + context(`if the use of a first-party ID has been enabled ${caseTitle}`, () => { + context('and the response includes a first-party ID', () => { + context('and the enabled storage types include "cookie"', () => { + it('should store the provided first-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); + + context('and the enabled storage types include "html5"', () => { + it('should store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided first-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); + }); + }); + + context('and the response lacks a first-party ID', () => { + it('should wipe any existing first-party ID from storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + ...opts }, - storage: { - type: 'cookie', - expires: 30 - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); const [request] = server.requests; + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo', - fp: 'bar' + envelope: 'foo' // no 'fp' field }, expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.true; + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdFp')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); + }); + }); + }); + + context(`if the use of a supplemental third-party ID has been enabled ${caseTitle}`, () => { + context('and the response includes a third-party ID', () => { + context('and the enabled storage type include "cookie"', () => { + it('should store the provided third-party ID in a cookie', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie' ], + storage: { expires: 30 } + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + }); + }); + + context('and the enabled storage types include "html5"', () => { + it('should store the provided third-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.true; + + setDataInLocalStorage.restore(); + }); + }); + + context('and the enabled storage types are "cookie" and "html5"', () => { + it('should store the provided third-party ID in each storage type', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + ...opts + }, + enabledStorageTypes: [ 'cookie', 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + tp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.true; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.true; + + setCookie.restore(); + domainUtils.domainOverride.restore(); + setDataInLocalStorage.restore(); + }); }); }); - context('and the storage type is "html5"', () => { - it('should store the provided first-party ID in local storage', () => { + context('and the response lacks a third-party ID', () => { + it('should wipe any existing third-party ID from storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + ...opts }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); const [request] = server.requests; - const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); + const setCookie = sinon.stub(storage, 'setCookie'); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo', - fp: 'bar' + envelope: 'foo' // no 'tp' field }, expires: 1645667805067 })); - expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.true; + expect(removeDataFromLocalStorage.calledWithExactly('33acrossIdTp')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdTp', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; - setDataInLocalStorage.restore(); + removeDataFromLocalStorage.restore(); + setCookie.restore(); + domainUtils.domainOverride.restore(); }); }); }); + }); - context('and the response lacks a first-party ID', () => { - it('should wipe any existing first-party ID from storage', () => { + context('if the use of a first-party ID has been disabled', () => { + context('and the response includes a first-party ID', () => { + it('should not store the provided first-party ID in a cookie', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { pid: '12345', - storeFpid: true + storeFpid: false }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'html5' + expires: 30 } }); @@ -148,42 +398,74 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; - const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ succeeded: true, data: { - envelope: 'foo' // no 'fp' field + envelope: 'foo', + fp: 'bar' }, expires: 1645667805067 })); - expect(removeDataFromLocalStorage.calledOnceWithExactly('33acrossIdFp')).to.be.true; - expect(setCookie.calledOnceWithExactly('33acrossIdFp', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax', 'foo.com')).to.be.false; - removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); + }); + + it('should not store the provided first-party ID in local storage', () => { + const completeCallback = () => {}; + + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345', + storeFpid: false + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + callback(completeCallback); + + const [request] = server.requests; + + const setDataInLocalStorage = sinon.stub(storage, 'setDataInLocalStorage'); + + request.respond(200, { + 'Content-Type': 'application/json' + }, JSON.stringify({ + succeeded: true, + data: { + envelope: 'foo', + fp: 'bar' + }, + expires: 1645667805067 + })); + + expect(setDataInLocalStorage.calledWithExactly('33acrossIdFp', 'bar')).to.be.false; + + setDataInLocalStorage.restore(); }); }); }); - context('if the use of a first-party ID has been disabled (default value)', () => { - context('and the response includes a first-party ID', () => { - it('should not store the provided first-party ID in a cookie', () => { + context('if the use of a supplemental third-party ID has been disabled', () => { + context('and the response includes a third-party ID', () => { + it('should not store the provided third-party ID in a cookie', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { - pid: '12345' - // no storeFpid param + pid: '12345', + storeTpid: false }, + enabledStorageTypes: [ 'cookie' ], storage: { - type: 'cookie', expires: 30 } }); @@ -193,7 +475,6 @@ describe('33acrossIdSystem', () => { const [request] = server.requests; const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); request.respond(200, { 'Content-Type': 'application/json' @@ -201,28 +482,26 @@ describe('33acrossIdSystem', () => { succeeded: true, data: { envelope: 'foo', - fp: 'bar' + tp: 'bar' }, expires: 1645667805067 })); - expect(setCookie.calledOnceWithExactly('33acrossIdFp', 'bar', sinon.match.string, 'Lax')).to.be.false; + expect(setCookie.calledWithExactly('33acrossIdTp', 'bar', sinon.match.string, 'Lax')).to.be.false; setCookie.restore(); - cookiesAreEnabled.restore(); }); - it('should not store the provided first-party ID in local storage', () => { + it('should not store the provided third-party ID in local storage', () => { const completeCallback = () => {}; const { callback } = thirthyThreeAcrossIdSubmodule.getId({ params: { - pid: '12345' - // no storeFpid param + pid: '12345', + storeTpid: false }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -237,12 +516,12 @@ describe('33acrossIdSystem', () => { succeeded: true, data: { envelope: 'foo', - fp: 'bar' + tp: 'bar' }, expires: 1645667805067 })); - expect(setDataInLocalStorage.calledOnceWithExactly('33acrossIdFp', 'bar')).to.be.false; + expect(setDataInLocalStorage.calledWithExactly('33acrossIdTp', 'bar')).to.be.false; setDataInLocalStorage.restore(); }); @@ -257,9 +536,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); callback(completeCallback); @@ -268,7 +546,7 @@ describe('33acrossIdSystem', () => { const removeDataFromLocalStorage = sinon.stub(storage, 'removeDataFromLocalStorage'); const setCookie = sinon.stub(storage, 'setCookie'); - const cookiesAreEnabled = sinon.stub(storage, 'cookiesAreEnabled').returns(true); + sinon.stub(domainUtils, 'domainOverride').returns('foo.com'); request.respond(200, { 'Content-Type': 'application/json' @@ -281,11 +559,11 @@ describe('33acrossIdSystem', () => { })); expect(removeDataFromLocalStorage.calledWith('33acrossId')).to.be.true; - expect(setCookie.calledWith('33acrossId', '', sinon.match.string, 'Lax')).to.be.true; + expect(setCookie.calledWithExactly('33acrossId', '', sinon.match.string, 'Lax', 'foo.com')).to.be.true; removeDataFromLocalStorage.restore(); setCookie.restore(); - cookiesAreEnabled.restore(); + domainUtils.domainOverride.restore(); }); }); @@ -499,9 +777,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'html5' - } + enabledStorageTypes: [ 'html5' ], + storage: {} }); sinon.stub(storage, 'getDataFromLocalStorage') @@ -525,9 +802,8 @@ describe('33acrossIdSystem', () => { params: { pid: '12345' }, - storage: { - type: 'cookie' - } + enabledStorageTypes: [ 'cookie' ], + storage: {} }); sinon.stub(storage, 'getCookie') @@ -561,6 +837,73 @@ describe('33acrossIdSystem', () => { }); }); + context('when a third-party ID is present in local storage', () => { + it('should call endpoint with the encoded third-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'html5' ], + storage: {} + }); + + sinon.stub(storage, 'getDataFromLocalStorage') + .withArgs('33acrossIdTp') + .returns('33acrossIdTpValue+'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('tp=33acrossIdTpValue%2B'); + + storage.getDataFromLocalStorage.restore(); + }); + }); + + context('when a third-party ID is present in cookie storage', () => { + it('should call endpoint with the third-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + }, + enabledStorageTypes: [ 'cookie' ], + storage: {} + }); + + sinon.stub(storage, 'getCookie') + .withArgs('33acrossIdTp') + .returns('33acrossIdTpValue'); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).to.contain('tp=33acrossIdTpValue'); + + storage.getCookie.restore(); + }); + }); + + context('when a third-party ID is not present in storage', () => { + it('should not call endpoint with the third-party ID included', () => { + const completeCallback = () => {}; + const { callback } = thirthyThreeAcrossIdSubmodule.getId({ + params: { + pid: '12345' + } + }); + + callback(completeCallback); + + const [request] = server.requests; + + expect(request.url).not.to.contain('tp='); + }); + }); + context('when the partner ID is not given', () => { it('should log an error', () => { const logErrorSpy = sinon.spy(utils, 'logError'); @@ -817,4 +1160,25 @@ describe('33acrossIdSystem', () => { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(thirthyThreeAcrossIdSubmodule); + }) + it('33acrossId', function() { + const userId = { + '33acrossId': { + envelope: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: '33across.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/51DegreesRtdProvider_spec.js b/test/spec/modules/51DegreesRtdProvider_spec.js new file mode 100644 index 00000000000..7b60a08b906 --- /dev/null +++ b/test/spec/modules/51DegreesRtdProvider_spec.js @@ -0,0 +1,485 @@ +import { + extractConfig, + get51DegreesJSURL, + is51DegreesMetaPresent, + deepSetNotEmptyValue, + convert51DegreesDataToOrtb2, + convert51DegreesDeviceToOrtb2, + getBidRequestData, + fiftyOneDegreesSubmodule, +} from 'modules/51DegreesRtdProvider'; +import {mergeDeep} from '../../../src/utils'; + +const inject51DegreesMeta = () => { + const meta = document.createElement('meta'); + meta.httpEquiv = 'Delegate-CH'; + meta.content = 'sec-ch-ua-full-version-list https://cloud.51degrees.com; sec-ch-ua-model https://cloud.51degrees.com; sec-ch-ua-platform https://cloud.51degrees.com; sec-ch-ua-platform-version https://cloud.51degrees.com'; + document.head.appendChild(meta); +}; + +describe('51DegreesRtdProvider', function() { + const fiftyOneDegreesDevice = { + screenpixelswidth: 5120, + screenpixelsheight: 1440, + hardwarevendor: 'Apple', + hardwaremodel: 'Macintosh', + hardwarename: [ + 'Macintosh', + ], + platformname: 'macOS', + platformversion: '14.1.2', + screeninchesheight: 13.27, + screenincheswidth: 47.17, + devicetype: 'Desktop', + pixelratio: 1, + deviceid: '17595-131215-132535-18092', + }; + + const fiftyOneDegreesDeviceX2scaling = { + ...fiftyOneDegreesDevice, + screenpixelsheight: fiftyOneDegreesDevice.screenpixelsheight / 2, + screenpixelswidth: fiftyOneDegreesDevice.screenpixelswidth / 2, + screenpixelsphysicalheight: fiftyOneDegreesDevice.screenpixelsheight, + screenpixelsphysicalwidth: fiftyOneDegreesDevice.screenpixelswidth, + pixelratio: fiftyOneDegreesDevice.pixelratio * 2, + }; + + const fiftyOneDegreesData = { + device: fiftyOneDegreesDevice, + }; + + const expectedORTB2DeviceResult = { + device: { + devicetype: 2, + make: 'Apple', + model: 'Macintosh', + os: 'macOS', + osv: '14.1.2', + h: 1440, + w: 5120, + ppi: 109, + pxratio: 1, + ext: { + fiftyonedegrees_deviceId: '17595-131215-132535-18092', + }, + }, + }; + + const expectedORTB2Result = {}; + mergeDeep( + expectedORTB2Result, + expectedORTB2DeviceResult, + // placeholder for the next 51Degrees RTD submodule update + ); + + describe('extractConfig', function() { + it('returns the resourceKey from the moduleConfig', function() { + const reqBidsConfigObj = {}; + const moduleConfig = {params: {resourceKey: 'TEST_RESOURCE_KEY'}}; + expect(extractConfig(moduleConfig, reqBidsConfigObj)).to.deep.equal({ + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: undefined, + }); + }); + + it('returns the onPremiseJSUrl from the moduleConfig', function() { + const reqBidsConfigObj = {}; + const moduleConfig = {params: {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'}}; + expect(extractConfig(moduleConfig, reqBidsConfigObj)).to.deep.equal({ + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + resourceKey: undefined, + }); + }); + + it('throws an error if neither resourceKey nor onPremiseJSUrl is provided', function() { + const reqBidsConfigObj = {}; + const moduleConfig = {params: {}}; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }); + + it('throws an error if both resourceKey and onPremiseJSUrl are provided', function() { + const reqBidsConfigObj = {}; + const moduleConfig = {params: { + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + }}; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }); + + it('throws an error if the resourceKey is equal to "" from example', function() { + const reqBidsConfigObj = {}; + const moduleConfig = {params: {resourceKey: ''}}; + expect(() => extractConfig(moduleConfig, reqBidsConfigObj)).to.throw(); + }); + + it('sets the resourceKey to undefined if it was set to "0"', function() { + const moduleConfig = {params: { + resourceKey: '0', + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + }}; + expect(extractConfig(moduleConfig, {})).to.deep.equal({ + resourceKey: undefined, + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + }); + }); + + it('sets the onPremiseJSUrl to undefined if it was set to "0"', function() { + const moduleConfig = {params: { + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: '0', + }}; + expect(extractConfig(moduleConfig, {})).to.deep.equal({ + resourceKey: 'TEST_RESOURCE_KEY', + onPremiseJSUrl: undefined, + }); + }); + + it('throws an error if the onPremiseJSUrl is not a valid URL', function() { + expect(() => extractConfig({ + params: {onPremiseJSUrl: 'invalid URL'} + }, {})).to.throw(); + expect(() => extractConfig({ + params: {onPremiseJSUrl: 'www.example.com/51Degrees.core.js'} + }, {})).to.throw(); + }); + + it('allows the onPremiseJSUrl to be a valid URL', function() { + const VALID_URLS = [ + 'https://www.example.com/51Degrees.core.js', + 'http://example.com/51Degrees.core.js', + '//example.com/51Degrees.core.js', + '/51Degrees.core.js', + ]; + + VALID_URLS.forEach(url => { + expect(() => extractConfig({ + params: {onPremiseJSUrl: url} + }, {})).to.not.throw(); + }); + }); + }); + + describe('get51DegreesJSURL', function() { + const hev = { + 'brands': [ + { + 'brand': 'Chromium', + 'version': '130' + }, + { + 'brand': 'Google Chrome', + 'version': '130' + }, + { + 'brand': 'Not?A_Brand', + 'version': '99' + } + ], + 'fullVersionList': [ + { + 'brand': 'Chromium', + 'version': '130.0.6723.92' + }, + { + 'brand': 'Google Chrome', + 'version': '130.0.6723.92' + }, + { + 'brand': 'Not?A_Brand', + 'version': '99.0.0.0' + } + ], + 'mobile': false, + 'model': '', + 'platform': 'macOS', + 'platformVersion': '14.6.1' + }; + const mockWindow = { + ...window, + screen: { + height: 1117, + width: 1728, + }, + devicePixelRatio: 2, + }; + + it('returns the cloud URL if the resourceKey is provided', function() { + const config = {resourceKey: 'TEST_RESOURCE_KEY'}; + expect(get51DegreesJSURL(config, mockWindow)).to.equal( + 'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js?' + + `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` + + `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` + + `51D_PixelRatio=${mockWindow.devicePixelRatio}` + ); + }); + + it('returns the on-premise URL if the onPremiseJSUrl is provided', function () { + const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'}; + expect(get51DegreesJSURL(config, mockWindow)).to.equal( + `https://example.com/51Degrees.core.js?` + + `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` + + `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` + + `51D_PixelRatio=${mockWindow.devicePixelRatio}` + ); + }); + + it('doesn\'t override static query string parameters', function () { + const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js?test=1'}; + expect(get51DegreesJSURL(config, mockWindow)).to.equal( + `https://example.com/51Degrees.core.js?test=1&` + + `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` + + `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` + + `51D_PixelRatio=${mockWindow.devicePixelRatio}` + ); + }); + + it('adds high entropy values to the query string, if available', async function () { + const config = { + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + hev, + }; + expect(get51DegreesJSURL(config, mockWindow)).to.equal( + `https://example.com/51Degrees.core.js?` + + `51D_GetHighEntropyValues=${btoa(JSON.stringify(hev))}&` + + `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` + + `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` + + `51D_PixelRatio=${mockWindow.devicePixelRatio}` + ); + }); + + it('doesn\'t add high entropy values to the query string if object is empty', function () { + const config = { + onPremiseJSUrl: 'https://example.com/51Degrees.core.js', + hev: {}, + }; + expect(get51DegreesJSURL(config, mockWindow)).to.equal( + `https://example.com/51Degrees.core.js?` + + `51D_ScreenPixelsHeight=${mockWindow.screen.height}&` + + `51D_ScreenPixelsWidth=${mockWindow.screen.width}&` + + `51D_PixelRatio=${mockWindow.devicePixelRatio}` + ); + }); + + it('keeps the original URL if none of the additional parameters are available', function () { + // delete screen and devicePixelRatio properties to test the case when they are not available + delete mockWindow.screen; + delete mockWindow.devicePixelRatio; + + const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'}; + expect(get51DegreesJSURL(config, mockWindow)).to.equal('https://example.com/51Degrees.core.js'); + expect(get51DegreesJSURL(config, window)).to.not.equal('https://example.com/51Degrees.core.js'); + }); + }); + + describe('is51DegreesMetaPresent', function() { + let initialHeadInnerHTML; + + before(function() { + initialHeadInnerHTML = document.head.innerHTML; + }); + + afterEach(function() { + document.head.innerHTML = initialHeadInnerHTML; + }); + + it('returns true if the 51Degrees meta tag is present', function () { + inject51DegreesMeta(); + expect(is51DegreesMetaPresent()).to.be.true; + }); + + it('returns false if the 51Degrees meta tag is not present', function() { + expect(is51DegreesMetaPresent()).to.be.false; + }); + + it('works with multiple meta tags, even if those are not to include any `content`', function() { + const meta1 = document.createElement('meta'); + meta1.httpEquiv = 'Delegate-CH'; + document.head.appendChild(meta1); + + inject51DegreesMeta(); + + const meta2 = document.createElement('meta'); + meta2.httpEquiv = 'Delegate-CH'; + document.head.appendChild(meta2); + + expect(is51DegreesMetaPresent()).to.be.true; + }); + }); + + describe('deepSetNotEmptyValue', function() { + it('sets value of ORTB2 key if it is not empty', function() { + const data = {}; + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', 'TEST_ORTB2_VALUE'); + expect(data).to.deep.equal({TEST_ORTB2_KEY: 'TEST_ORTB2_VALUE'}); + deepSetNotEmptyValue(data, 'test2.TEST_ORTB2_KEY_2', 'TEST_ORTB2_VALUE_2'); + expect(data).to.deep.equal({ + TEST_ORTB2_KEY: 'TEST_ORTB2_VALUE', + test2: { + TEST_ORTB2_KEY_2: 'TEST_ORTB2_VALUE_2' + }, + }); + }); + + it('throws an error if the key is empty', function() { + const data = {}; + expect(() => deepSetNotEmptyValue(data, '', 'TEST_ORTB2_VALUE')).to.throw(); + }); + + it('does not set value of ORTB2 key if it is empty', function() { + const data = {}; + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', ''); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', 0); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', null); + deepSetNotEmptyValue(data, 'TEST_ORTB2_KEY', undefined); + deepSetNotEmptyValue(data, 'TEST.TEST_ORTB2_KEY', undefined); + expect(data).to.deep.equal({}); + }); + }); + + describe('convert51DegreesDataToOrtb2', function() { + it('returns empty object if data is null, undefined or empty', () => { + expect(convert51DegreesDataToOrtb2(null)).to.deep.equal({}); + expect(convert51DegreesDataToOrtb2(undefined)).to.deep.equal({}); + expect(convert51DegreesDataToOrtb2({})).to.deep.equal({}); + }); + + it('converts all 51Degrees data to ORTB2 format', function() { + expect(convert51DegreesDataToOrtb2(fiftyOneDegreesData)).to.deep.equal(expectedORTB2Result); + }); + }); + + describe('convert51DegreesDeviceToOrtb2', function() { + it('converts 51Degrees device data to ORTB2 format', function() { + expect( + convert51DegreesDeviceToOrtb2(fiftyOneDegreesDevice) + ).to.deep.equal(expectedORTB2DeviceResult); + }); + + it('returns an empty object if the device data is not provided', function() { + expect(convert51DegreesDeviceToOrtb2()).to.deep.equal({}); + }); + + it('does not set the deviceid if it is not provided', function() { + const device = {...fiftyOneDegreesDevice}; + delete device.deviceid; + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('ext'); + }); + + it('sets the model to hardwarename if hardwaremodel is not provided', function() { + const device = {...fiftyOneDegreesDevice}; + delete device.hardwaremodel; + expect(convert51DegreesDeviceToOrtb2(device).device).to.deep.include({model: 'Macintosh'}); + }); + + it('does not set the model if hardwarename is empty', function() { + const device = {...fiftyOneDegreesDevice}; + delete device.hardwaremodel; + device.hardwarename = []; + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('model'); + }); + + it('does not set the ppi if screeninchesheight is not provided', function() { + const device = {...fiftyOneDegreesDevice}; + delete device.screeninchesheight; + expect(convert51DegreesDeviceToOrtb2(device).device).to.not.have.any.keys('ppi'); + }); + + it('sets correct ppi if screenpixelsphysicalheight & screeninchesheight are provided', function() { + expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDeviceX2scaling).device).to.deep.include({ + ppi: expectedORTB2DeviceResult.device.ppi, + }); + }); + + it('if screenpixelsphysical properties are available, use them for screen size', function() { + expect(fiftyOneDegreesDevice.screenpixelswidth).to.not.equal(fiftyOneDegreesDeviceX2scaling.screenpixelswidth); + expect(fiftyOneDegreesDevice.screenpixelsheight).to.not.equal(fiftyOneDegreesDeviceX2scaling.screenpixelsheight); + expect(fiftyOneDegreesDevice.screenpixelsphysicalwidth).to.equal(undefined); + expect(fiftyOneDegreesDevice.screenpixelsphysicalheight).to.equal(undefined); + expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDeviceX2scaling).device).to.deep.include({ + h: expectedORTB2DeviceResult.device.h, + w: expectedORTB2DeviceResult.device.w, + }); + }); + }); + + describe('getBidRequestData', function() { + let initialHeadInnerHTML; + let reqBidsConfigObj = {}; + const resetReqBidsConfigObj = () => { + reqBidsConfigObj = { + ortb2Fragments: { + global: { + device: {}, + }, + }, + }; + }; + + before(function() { + initialHeadInnerHTML = document.head.innerHTML; + + const mockScript = document.createElement('script'); + mockScript.innerHTML = ` + window.fod = {complete: (_callback) => _callback(${JSON.stringify(fiftyOneDegreesData)})}; + `; + document.head.appendChild(mockScript); + }); + + beforeEach(function() { + resetReqBidsConfigObj(); + }); + + after(function() { + document.head.innerHTML = initialHeadInnerHTML; + }); + + it('calls the callback even if submodule fails (wrong config)', function() { + const callback = sinon.spy(); + const moduleConfig = {params: {}}; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + expect(callback.calledOnce).to.be.true; + }); + + it('calls the callback even if submodule fails (on-premise, non-working URL)', async function() { + const callback = sinon.spy(); + const moduleConfig = {params: {onPremiseJSUrl: 'http://localhost:12345/test/51Degrees.core.js'}}; + + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(callback.calledOnce).to.be.true; + }); + + it('calls the callback even if submodule fails (invalid resource key)', async function() { + const callback = sinon.spy(); + const moduleConfig = {params: {resourceKey: 'INVALID_RESOURCE_KEY'}}; + + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(callback.calledOnce).to.be.true; + }); + + it('works with Delegate-CH meta tag', async function() { + inject51DegreesMeta(); + const callback = sinon.spy(); + const moduleConfig = {params: {resourceKey: 'INVALID_RESOURCE_KEY'}}; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(callback.calledOnce).to.be.true; + }); + + it('has the correct ORTB2 data', async function() { + const callback = sinon.spy(); + const moduleConfig = {params: {resourceKey: 'INVALID_RESOURCE_KEY'}}; + getBidRequestData(reqBidsConfigObj, callback, moduleConfig, {}); + await new Promise(resolve => setTimeout(resolve, 100)); + expect(callback.calledOnce).to.be.true; + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal(expectedORTB2Result); + }); + }); + + describe('init', function() { + it('initialises the 51Degrees RTD provider', function() { + expect(fiftyOneDegreesSubmodule.init()).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js b/test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js similarity index 98% rename from test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js rename to test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js index 9241fda8c81..57fb5b9a32b 100644 --- a/test/spec/modules/prebidmanagerAnalyticsAdapter_spec.js +++ b/test/spec/modules/AsteriobidPbmAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import prebidmanagerAnalytics, {storage} from 'modules/prebidmanagerAnalyticsAdapter.js'; +import prebidmanagerAnalytics, {storage} from 'modules/AsteriobidPbmAnalyticsAdapter.js'; import {expect} from 'chai'; import {server} from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; diff --git a/test/spec/modules/BTBidAdapter_spec.js b/test/spec/modules/BTBidAdapter_spec.js index e0306abb7f0..2ec0acc424e 100644 --- a/test/spec/modules/BTBidAdapter_spec.js +++ b/test/spec/modules/BTBidAdapter_spec.js @@ -7,11 +7,10 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/consentManagementGpp.js'; -import 'modules/enrichmentFpdModule.js'; -import 'modules/gdprEnforcement.js'; +import 'modules/tcfControl.js'; import 'modules/gppControl_usnat.js'; import 'modules/schain.js'; diff --git a/test/spec/modules/a1MediaBidAdapter_spec.js b/test/spec/modules/a1MediaBidAdapter_spec.js index e1db2b9ad8d..8031b584d65 100644 --- a/test/spec/modules/a1MediaBidAdapter_spec.js +++ b/test/spec/modules/a1MediaBidAdapter_spec.js @@ -75,7 +75,8 @@ const getConvertedBidReq = () => { }, bidfloor: 0, bidfloorcur: 'JPY', - id: '2e9f38ea93bb9e' + id: '2e9f38ea93bb9e', + secure: 1 } ], test: 0, diff --git a/test/spec/modules/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index 31ef9dd6466..ecc40025c95 100644 --- a/test/spec/modules/acuityadsBidAdapter_spec.js +++ b/test/spec/modules/acuityadsBidAdapter_spec.js @@ -1,11 +1,21 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/acuityadsBidAdapter'; +import { spec } from '../../../modules/acuityadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'acuityads' +const bidder = 'acuityads'; describe('AcuityAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('AcuityAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,12 +86,22 @@ describe('AcuityAdsBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, - timeout: 500, - ortb2: {} + timeout: 500 }; describe('isBidRequestValid', function () { @@ -113,6 +136,7 @@ describe('AcuityAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -130,7 +154,7 @@ describe('AcuityAdsBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -146,6 +170,7 @@ describe('AcuityAdsBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -171,8 +196,10 @@ describe('AcuityAdsBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -187,40 +214,38 @@ describe('AcuityAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); - describe('Returns data with gppConsent', function () { - it('bidderRequest.gppConsent', () => { - bidderRequest.gppConsent = { - gppString: 'abc123', - applicableSections: [8] - }; + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - serverRequest = spec.buildRequests(bids, bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); - delete bidderRequest.gppConsent; - }) + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); - it('bidderRequest.ortb2.regs.gpp', () => { - bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; - bidderRequest.ortb2.regs.gpp = 'abc123'; - bidderRequest.ortb2.regs.gpp_sid = [8]; + delete bidderRequest.gppConsent; + }) - serverRequest = spec.buildRequests(bids, bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.property('gpp'); - expect(data).to.have.property('gpp_sid'); - }) - }); + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -424,5 +449,17 @@ describe('AcuityAdsBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.admanmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/adagioAnalyticsAdapter_spec.js b/test/spec/modules/adagioAnalyticsAdapter_spec.js index c14393e267b..d1058170f44 100644 --- a/test/spec/modules/adagioAnalyticsAdapter_spec.js +++ b/test/spec/modules/adagioAnalyticsAdapter_spec.js @@ -1,9 +1,9 @@ -import adagioAnalyticsAdapter from 'modules/adagioAnalyticsAdapter.js'; -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; import * as prebidGlobal from 'src/prebidGlobal.js'; +import * as utils from 'src/utils.js'; +import adagioAnalyticsAdapter, { _internal } from 'modules/adagioAnalyticsAdapter.js'; import { EVENTS } from 'src/constants.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; let adapterManager = require('src/adapterManager').default; let events = require('src/events'); @@ -14,30 +14,35 @@ describe('adagio analytics adapter - adagio.js', () => { beforeEach(() => { sandbox = sinon.createSandbox(); - sandbox.stub(events, 'getEvents').returns([]); - const w = utils.getWindowTop(); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'] + }); adapterManager.registerAnalyticsAdapter({ code: 'adagio', adapter: adagioAnalyticsAdapter }); - w.ADAGIO = w.ADAGIO || {}; - w.ADAGIO.queue = w.ADAGIO.queue || []; + _internal.getAdagioNs().pageviewId = 'a68e6d70-213b-496c-be0a-c468ff387106'; - adagioQueuePushSpy = sandbox.spy(w.ADAGIO.queue, 'push'); + adagioQueuePushSpy = sandbox.spy(_internal.getAdagioNs().queue, 'push'); }); afterEach(() => { + _internal.getAdagioNs().queue = []; sandbox.restore(); }); describe('track', () => { beforeEach(() => { adapterManager.enableAnalytics({ - provider: 'adagio' + provider: 'adagio', + options: { + organizationId: '1001', + site: 'test-com', + } }); }); @@ -93,7 +98,6 @@ describe('adagio analytics adapter - adagio.js', () => { // Step 1-3: Send events Object.entries(testEvents).forEach(([ev, payload]) => events.emit(ev, payload)); - function eventItem(eventName, args) { return sinon.match({ action: 'pb-analytics-event', @@ -108,81 +112,15 @@ describe('adagio analytics adapter - adagio.js', () => { Object.entries(testEvents).forEach(([ev, payload]) => sinon.assert.calledWith(adagioQueuePushSpy, eventItem(ev, payload))); }); }); - - describe('no track', () => { - beforeEach(() => { - sandbox.stub(utils, 'getWindowTop').throws(); - - adapterManager.enableAnalytics({ - provider: 'adagio' - }); - }); - - afterEach(() => { - adagioAnalyticsAdapter.disableAnalytics(); - sandbox.restore(); - }); - - it('builds and sends auction data', () => { - let bidRequest = { - bids: [{ - adUnitCode: 'div-1', - params: { - features: { - siteId: '2', - placement: 'pave_top', - pagetype: 'article', - category: 'IAB12,IAB12-2', - device: '2', - } - } - }, { - adUnitCode: 'div-2', - params: { - features: { - siteId: '2', - placement: 'ban_top', - pagetype: 'article', - category: 'IAB12,IAB12-2', - device: '2', - } - }, - }], - }; - let bidResponse = { - bidderCode: 'adagio', - width: 300, - height: 250, - statusMessage: 'Bid available', - cpm: 6.2189757658226075, - currency: '', - netRevenue: false, - adUnitCode: 'div-1', - timeToRespond: 132, - }; - - // Step 1: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, bidRequest); - - // Step 2: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bidResponse); - - // Step 3: Send auction end event - events.emit(EVENTS.AUCTION_END, {}); - - utils.getWindowTop.restore(); - - sandbox.assert.callCount(adagioQueuePushSpy, 0); - }); - }); }); const AUCTION_ID = '25c6d7f5-699a-4bfc-87c9-996f915341fa'; -const AUCTION_ID_ADAGIO = '6fc53663-bde5-427b-ab63-baa9ed296f47' +const RTD_AUCTION_ID = '753b3784-12a1-44c2-9d08-d0e4ee910e69'; +const RTD_AUCTION_ID_CACHE = '04d991be-8f7d-4491-930b-2b7eefb3c447'; const AUCTION_ID_CACHE = 'b43d24a0-13d4-406d-8176-3181402bafc4'; -const AUCTION_ID_CACHE_ADAGIO = 'a9cae98f-efb5-477e-9259-27350044f8db'; +const SESSION_ID = 'c4f9e517-a592-45af-9560-ca191823d591'; -const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { +const BID_ADAGIO = { bidder: 'adagio', auctionId: AUCTION_ID, adUnitCode: '/19968336/header-bid-tag-1', @@ -215,9 +153,9 @@ const BID_ADAGIO = Object.assign({}, BID_ADAGIO, { sid: '42', e_pba_test: true } -}); +}; -const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { +const BID_ANOTHER = { bidder: 'another', auctionId: AUCTION_ID, adUnitCode: '/19968336/header-bid-tag-1', @@ -246,7 +184,7 @@ const BID_ANOTHER = Object.assign({}, BID_ANOTHER, { meta: { advertiserDomains: ['example.com'] } -}); +}; const BID_CACHED = Object.assign({}, BID_ADAGIO, { auctionId: AUCTION_ID_CACHE, @@ -254,14 +192,20 @@ const BID_CACHED = Object.assign({}, BID_ADAGIO, { }); const PARAMS_ADG = { - organizationId: '1001', - site: 'test-com', - pageviewId: 'a68e6d70-213b-496c-be0a-c468ff387106', environment: 'desktop', +}; + +const ORTB_DATA = { pagetype: 'article', - placement: 'pave_top', - testName: 'test', - testVersion: 'version', +}; + +const ADG_RTD = { + 'uid': RTD_AUCTION_ID, + 'session': { + 'testName': 'test', + 'testVersion': 'version', + 'id': SESSION_ID, + } }; const AUCTION_INIT_ANOTHER = { @@ -300,8 +244,20 @@ const AUCTION_INIT_ANOTHER = { 'params': { ...PARAMS_ADG }, + }, { + 'bidder': 'anotherWithAlias', + 'params': { + 'publisherId': '1001' + }, }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, }, { 'code': '/19968336/footer-bid-tag-1', 'mediaTypes': { @@ -321,103 +277,202 @@ const AUCTION_INIT_ANOTHER = { 'publisherId': '1001' }, } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, } ], 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], - 'bidderRequests': [ { - 'bidderCode': 'another', - 'auctionId': AUCTION_ID, - 'bidderRequestId': '1be65d7958826a', - 'bids': [ { - 'bidder': 'another', - 'params': { - 'publisherId': '1001', + 'bidderRequests': [ + { + 'bidderCode': 'another', + 'auctionId': AUCTION_ID, + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'another', + 'params': { + 'publisherId': '1001', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + }, + { + 'bidder': 'another', + 'params': { + 'publisherId': '1001' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/footer-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + }, + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', + } + }, + { + 'bidderCode': 'nobid', 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - }, { - 'bidder': 'another', - 'params': { - 'publisherId': '1001' + 'bidderRequestId': '1be65d7958826a', + 'bids': [{ + 'bidder': 'nobid', + 'params': { + 'publisherId': '1001' + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } } - }, - 'adUnitCode': '/19968336/footer-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', + } + }, + { + bidderCode: 'anotherWithAlias', 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - }, { - 'bidder': 'nobid', - 'params': { - 'publisherId': '1001' + 'bidderRequestId': '1be65d7958826a', + 'bids': [ + { + 'bidder': 'anotherWithAlias', + 'params': { + 'publisherId': '1001', + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + }, + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } } - }, - 'adUnitCode': '/19968336/footer-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', + } + }, + { + 'bidderCode': 'adagio', 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] - } - }, { - 'bidderCode': 'adagio', - 'auctionId': AUCTION_ID, - 'bidderRequestId': '1be65d7958826a', - 'bids': [ { - 'bidder': 'adagio', - 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_ADAGIO + 'bidderRequestId': '1be65d7958826a', + 'bids': [ { + 'bidder': 'adagio', + 'params': { + ...PARAMS_ADG + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[640, 480]] + } + }, + 'adUnitCode': '/19968336/header-bid-tag-1', + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'sizes': [[640, 480]], + 'bidId': '2ecff0db240757', + 'bidderRequestId': '1be65d7958826a', + 'auctionId': AUCTION_ID, + 'src': 'client', + 'bidRequestsCount': 1 + } + ], + 'timeout': 3000, + 'refererInfo': { + 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] }, - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 480]] + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } } - }, - 'adUnitCode': '/19968336/header-bid-tag-1', - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', - 'sizes': [[640, 480]], - 'bidId': '2ecff0db240757', - 'bidderRequestId': '1be65d7958826a', - 'auctionId': AUCTION_ID, - 'src': 'client', - 'bidRequestsCount': 1 - } - ], - 'timeout': 3000, - 'refererInfo': { - 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + } } - } ], 'bidsReceived': [], 'winningBids': [], @@ -456,7 +511,14 @@ const AUCTION_INIT_CACHE = { ...PARAMS_ADG }, }, ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, }, { 'code': '/19968336/footer-bid-tag-1', 'mediaTypes': { @@ -476,7 +538,14 @@ const AUCTION_INIT_CACHE = { 'publisherId': '1001' }, } ], - 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014' + 'transactionId': 'ca4af27a-6d02-4f90-949d-d5541fa12014', + 'ortb2Imp': { + 'ext': { + 'data': { + 'placement': 'pave_top', + } + } + }, } ], 'adUnitCodes': ['/19968336/header-bid-tag-1', '/19968336/footer-bid-tag-1'], 'bidderRequests': [ { @@ -524,6 +593,19 @@ const AUCTION_INIT_CACHE = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD, + 'uid': RTD_AUCTION_ID_CACHE + }, + ...ORTB_DATA + } + } + } } }, { 'bidderCode': 'adagio', @@ -532,8 +614,7 @@ const AUCTION_INIT_CACHE = { 'bids': [ { 'bidder': 'adagio', 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_CACHE_ADAGIO + ...PARAMS_ADG }, 'mediaTypes': { 'banner': { @@ -553,6 +634,19 @@ const AUCTION_INIT_CACHE = { 'timeout': 3000, 'refererInfo': { 'topmostLocation': 'http://www.test.com/page.html', 'reachedTop': true, 'numIframes': 0, 'stack': ['http://www.test.com/page.html'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD, + 'uid': RTD_AUCTION_ID_CACHE + }, + ...ORTB_DATA + } + } + } } } ], @@ -569,6 +663,22 @@ const AUCTION_END_ANOTHER_NOBID = Object.assign({}, AUCTION_INIT_ANOTHER, { bidsReceived: [] }); +const PBS_ANALYTICS_ANOTHER = { + atag: [ + { + stage: 'auction-response', + module: 'adg-pba', + pba: { + '/19968336/header-bid-tag-1': { + st_id: '53', + splt_cs_id: '731' + } + } + } + ], + auctionId: AUCTION_ID, +} + const MOCK = { SET_TARGETING: { [BID_ADAGIO.adUnitCode]: BID_ADAGIO.adserverTargeting, @@ -582,6 +692,20 @@ const MOCK = { adagio: BID_ADAGIO, another: BID_ANOTHER }, + BID_TIMEOUT: { + another: [ + { + auctionId: AUCTION_ID, + adUnitCode: '/19968336/header-bid-tag-1', + bidder: 'another', + }, + { + auctionId: AUCTION_ID, + adUnitCode: '/19968336/footer-bid-tag-1', + bidder: 'another', + }, + ] + }, AUCTION_END: { another: AUCTION_END_ANOTHER, another_nobid: AUCTION_END_ANOTHER_NOBID @@ -621,10 +745,27 @@ describe('adagio analytics adapter', () => { let sandbox; beforeEach(() => { - sandbox = sinon.sandbox.create(); + sandbox = sinon.createSandbox(); sandbox.stub(events, 'getEvents').returns([]); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'], + convertCurrency: (cpm, from, to) => { + const convKeys = { + 'GBP-EUR': 0.7, + 'EUR-GBP': 1.3, + 'USD-EUR': 0.8, + 'EUR-USD': 1.2, + 'USD-GBP': 0.6, + 'GBP-USD': 1.6, + }; + return cpm * (convKeys[`${from}-${to}`] || 1); + } + }); + + _internal.getAdagioNs().pageviewId = 'a68e6d70-213b-496c-be0a-c468ff387106'; + adapterManager.registerAnalyticsAdapter({ code: 'adagio', adapter: adagioAnalyticsAdapter @@ -632,35 +773,28 @@ describe('adagio analytics adapter', () => { }); afterEach(() => { + _internal.getAdagioNs().queue = []; sandbox.restore(); }); describe('track', () => { beforeEach(() => { adapterManager.enableAnalytics({ - provider: 'adagio' + provider: 'adagio', + options: { + organizationId: '1001', + site: 'test-com', + } }); + adapterManager.aliasRegistry['anotherWithAlias'] = 'another'; }); afterEach(() => { adagioAnalyticsAdapter.disableAnalytics(); + delete adapterManager.aliasRegistry['anotherWithAlias']; }); it('builds and sends auction data', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({ - convertCurrency: (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - } - }); - events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); @@ -668,7 +802,7 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); - expect(server.requests.length).to.equal(3, 'requests count'); + expect(server.requests.length).to.equal(5, 'requests count'); { const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); @@ -676,7 +810,8 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.s_id).to.equal(SESSION_ID); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -686,7 +821,9 @@ describe('adagio analytics adapter', () => { expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another,nobid'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); } @@ -695,44 +832,52 @@ describe('adagio analytics adapter', () => { expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('1,1,0'); - expect(search.bdrs_cpm).to.equal('1.42,2.052,'); + expect(search.bdrs_bid).to.equal('1,1,0,0'); + expect(search.bdrs_cpm).to.equal('1.42,2.052,,'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.bdrs_timeout).to.equal('0'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.win_bdr).to.equal('another'); expect(search.win_mt).to.equal('ban'); expect(search.win_ban_sz).to.equal('728x90'); expect(search.win_net_cpm).to.equal('2.052'); expect(search.win_og_cpm).to.equal('2.592'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } }); it('builds and sends auction data with a cached bid win', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({ - convertCurrency: (cpm, from, to) => { - const convKeys = { - 'GBP-EUR': 0.7, - 'EUR-GBP': 1.3, - 'USD-EUR': 0.8, - 'EUR-USD': 1.2, - 'USD-GBP': 0.6, - 'GBP-USD': 1.6, - }; - return cpm * (convKeys[`${from}-${to}`] || 1); - } - }); - events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.bidcached); events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); @@ -741,15 +886,17 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.bidcached); events.emit(EVENTS.AD_RENDER_FAILED, MOCK.AD_RENDER_FAILED.bidcached); - expect(server.requests.length).to.equal(5, 'requests count'); + expect(server.requests.length).to.equal(8, 'requests count'); { + // the first request is getting cached we expect to see its auction id later when it's re-used const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID_CACHE); + expect(search.s_id).to.equal(SESSION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -760,6 +907,8 @@ describe('adagio analytics adapter', () => { expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); expect(search.bdrs).to.equal('adagio,another'); + expect(search.bdrs_code).to.equal('adagio,another'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); expect(search.t_n).to.equal('test'); expect(search.t_v).to.equal('version'); @@ -772,7 +921,30 @@ describe('adagio analytics adapter', () => { expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('1'); expect(search.pbjsv).to.equal('$prebid.version$'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID_CACHE); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.org_id).to.equal('1001'); + expect(search.site).to.equal('test-com'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.url_dmn).to.equal(window.location.hostname); + expect(search.pgtyp).to.equal('article'); + expect(search.plcmt).to.equal('pave_top'); + expect(search.mts).to.equal('ban'); + expect(search.ban_szs).to.equal('640x480'); + expect(search.bdrs).to.equal('another'); + expect(search.bdrs_code).to.equal('another'); + expect(search.bdrs_timeout).to.not.exist; + expect(search.adg_mts).to.not.exist; + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.pbjsv).to.equal('$prebid.version$'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.org_id).to.equal('1001'); expect(search.site).to.equal('test-com'); @@ -782,30 +954,59 @@ describe('adagio analytics adapter', () => { expect(search.plcmt).to.equal('pave_top'); expect(search.mts).to.equal('ban'); expect(search.ban_szs).to.equal('640x100,640x480'); - expect(search.bdrs).to.equal('adagio,another,nobid'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_code).to.equal('adagio,another,another,nobid'); + expect(search.bdrs_timeout).to.not.exist; expect(search.adg_mts).to.equal('ban'); } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.pv_id).to.equal('a68e6d70-213b-496c-be0a-c468ff387106'); + expect(search.bdrs_timeout).to.not.exist; + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('0,0,0'); - expect(search.bdrs_cpm).to.equal(',,'); + expect(search.bdrs_bid).to.equal('0,0,0,0'); + expect(search.bdrs_cpm).to.equal(',,,'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[5].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.rndr).to.not.exist; + expect(search.bdrs_timeout).to.equal('0'); + } + + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[6].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('3'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); - expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.auct_id_c).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); expect(search.win_bdr).to.equal('adagio'); expect(search.win_mt).to.equal('ban'); @@ -813,23 +1014,33 @@ describe('adagio analytics adapter', () => { expect(search.win_net_cpm).to.equal('1.42'); expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.not.exist; + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[4].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[7].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); expect(search.v).to.equal('4'); - expect(search.auct_id).to.equal(AUCTION_ID_ADAGIO); - expect(search.auct_id_c).to.equal(AUCTION_ID_CACHE_ADAGIO); + expect(search.auct_id).to.equal(RTD_AUCTION_ID); + expect(search.auct_id_c).to.equal(RTD_AUCTION_ID_CACHE); expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.win_bdr).to.equal('adagio'); + expect(search.win_mt).to.equal('ban'); + expect(search.win_ban_sz).to.equal('728x90'); + expect(search.win_net_cpm).to.equal('1.42'); + expect(search.win_og_cpm).to.equal('1.42'); expect(search.rndr).to.equal('0'); + expect(search.bdrs_timeout).to.equal('0,0,0,0'); } }); it('send an "empty" cpm when adserver currency != USD and convertCurrency() is undefined', () => { - sandbox.stub(prebidGlobal, 'getGlobal').returns({}); + sandbox.restore(); + sandbox.stub(prebidGlobal, 'getGlobal').returns({ + installedModules: ['adagioRtdProvider', 'rtdModule'] + }); events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); @@ -838,19 +1049,207 @@ describe('adagio analytics adapter', () => { events.emit(EVENTS.BID_WON, MOCK.BID_WON.another); events.emit(EVENTS.AD_RENDER_SUCCEEDED, MOCK.AD_RENDER_SUCCEEDED.another); - expect(server.requests.length).to.equal(3, 'requests count'); + expect(server.requests.length).to.equal(5, 'requests count'); // fail to compute bidder cpm and send an "empty" cpm { - const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); expect(protocol).to.equal('https'); expect(hostname).to.equal('c.4dex.io'); expect(pathname).to.equal('/pba.gif'); + expect(search.s_id).to.equal(SESSION_ID); expect(search.v).to.equal('2'); expect(search.e_sid).to.equal('42'); expect(search.e_pba_test).to.equal('true'); - expect(search.bdrs_bid).to.equal('1,1,0'); - expect(search.bdrs_cpm).to.equal('1.42,,'); + expect(search.bdrs_bid).to.equal('1,1,0,0'); + expect(search.bdrs_cpm).to.equal('1.42,,,'); + } + }); + + it('set adg-pbs aTags in beacon', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.PBS_ANALYTICS, PBS_ANALYTICS_ANOTHER); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + + // server.requests[0] -> AUCTION_INIT - AdUnit header-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[0].url); + + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.v).to.equal('1'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + + // server.requests[1] -> AUCTION_INIT - AdUnit footer-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[1].url); + + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.v).to.equal('1'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + + // server.requests[2] -> AUCTION_END - AdUnit header-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[2].url); + + expect(search.adu_code).to.equal('/19968336/header-bid-tag-1'); + expect(search.v).to.equal('2'); + + // The adg-pbs aTags fields are set in the beacon ! + expect(search.e_st_id).to.equal('53'); + expect(search.e_splt_cs_id).to.equal('731'); + } + + // server.requests[3] -> AUCTION_END - AdUnit footer-bid-tag-1 + { + const { search } = utils.parseUrl(server.requests[3].url); + + expect(search.adu_code).to.equal('/19968336/footer-bid-tag-1'); + expect(search.v).to.equal('2'); + + expect(search.e_st_id).to.be.undefined; + expect(search.e_splt_cs_id).to.be.undefined; + } + }); + + it('builds and sends auction data with a bid timeout', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.adagio); + events.emit(EVENTS.BID_TIMEOUT, MOCK.BID_TIMEOUT.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[0].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.bdrs_timeout).to.not.exist; + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[1].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('1'); + expect(search.bdrs_timeout).to.not.exist; + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[2].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.bdrs).to.equal('adagio,another,anotherWithAlias,nobid'); + expect(search.bdrs_timeout).to.equal('0,1,0,0'); + } + { + const { protocol, hostname, pathname, search } = utils.parseUrl(server.requests[3].url); + expect(protocol).to.equal('https'); + expect(hostname).to.equal('c.4dex.io'); + expect(pathname).to.equal('/pba.gif'); + expect(search.v).to.equal('2'); + expect(search.bdrs).to.equal('another'); + expect(search.bdrs_timeout).to.equal('1'); + } + }); + + it('builds and sends auction data with GAM slot callback', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + _internal.gamSlotCallback({ + slot: { + getAdUnitPath() { + return '/19968336/header-bid-tag-1' + }, + getSlotElementId() { + return '/19968336/header-bid-tag-1' + } + }, + isEmpty: true, + }); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + + expect(server.requests.length).to.equal(4, 'requests count'); + { + const { search } = utils.parseUrl(server.requests[0].url); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[1].url); + expect(search.v).to.equal('1'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[2].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.equal('gam'); + expect(search.adsrv_empty).to.equal('true'); + } + { + const { search } = utils.parseUrl(server.requests[3].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + }); + + it('builds and sends auction data with GAM slot callback after auction ended', () => { + events.emit(EVENTS.AUCTION_INIT, MOCK.AUCTION_INIT.another); + events.emit(EVENTS.BID_RESPONSE, MOCK.BID_RESPONSE.another); + events.emit(EVENTS.AUCTION_END, MOCK.AUCTION_END.another); + _internal.gamSlotCallback({ + slot: { + getAdUnitPath() { + return '/19968336/header-bid-tag-1' + }, + getSlotElementId() { + return '/19968336/header-bid-tag-1' + } + }, + isEmpty: true, + }); + + expect(server.requests.length).to.equal(5, 'requests count'); + { + const { search } = utils.parseUrl(server.requests[0].url); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[1].url); + expect(search.v).to.equal('1'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[2].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[3].url); + expect(search.v).to.equal('2'); + expect(search.adsrv).to.not.exist; + expect(search.adsrv_empty).to.not.exist; + } + { + const { search } = utils.parseUrl(server.requests[4].url); + expect(search.v).to.equal('3'); + expect(search.adsrv).to.equal('gam'); + expect(search.adsrv_empty).to.equal('true'); } }); }); diff --git a/test/spec/modules/adagioBidAdapter_spec.js b/test/spec/modules/adagioBidAdapter_spec.js index b11c0ab4a8e..75b0635bbef 100644 --- a/test/spec/modules/adagioBidAdapter_spec.js +++ b/test/spec/modules/adagioBidAdapter_spec.js @@ -1,22 +1,16 @@ -import { expect } from 'chai'; +import * as utils from '../../../src/utils.js'; import { - _features, - internal as adagio, - adagioScriptFromLocalStorageCb, - getAdagioScript, - storage, - setExtraParam, - spec, + BB_RENDERER_URL, ENDPOINT, VERSION, - BB_RENDERER_URL, - GlobalExchange + _internal, + setExtraParam, + spec } 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 { config } from '../../../src/config.js'; import { executeRenderer } from '../../../src/Renderer.js'; +import { expect } from 'chai'; import { userSync } from '../../../src/userSync.js'; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -73,7 +67,6 @@ const BidderRequestBuilder = function BidderRequestBuilder(options) { }; describe('Adagio bid adapter', () => { - let adagioMock; let utilsMock; let sandbox; let fakeRenderer; @@ -119,26 +112,16 @@ describe('Adagio bid adapter', () => { window.ADAGIO.versions.adagioBidderAdapter = VERSION; window.ADAGIO.pageviewId = 'dda61753-4059-4f75-b0bf-3f60bd2c4d9a'; - GlobalExchange.clearFeatures(); - GlobalExchange.clearExchangeData(); - - adagioMock = sinon.mock(adagio); utilsMock = sinon.mock(utils); - $$PREBID_GLOBAL$$.bidderSettings = { - adagio: { - storageAllowed: true - } - }; - sandbox = sinon.createSandbox(); + utilsMock = sandbox.mock(utils); }); afterEach(() => { window.ADAGIO = undefined; $$PREBID_GLOBAL$$.bidderSettings = {}; - adagioMock.restore(); utilsMock.restore(); sandbox.restore(); @@ -192,7 +175,7 @@ describe('Adagio bid adapter', () => { }); it('should compute organizationId and site params from global BidderSettings config', function() { - sandbox.stub(adagio, 'getRefererInfo').returns({ reachedTop: true }); + sandbox.stub(_internal, 'getRefererInfo').returns({ reachedTop: true }); sandbox.stub(config, 'getConfig').withArgs('adagio').returns({ siteId: '1000:SITE-NAME' }); @@ -266,10 +249,9 @@ describe('Adagio bid adapter', () => { 'user', 'schain', 'prebidVersion', - 'featuresVersion', + 'hasRtd', 'data', 'usIfr', - 'adgjs', ]; it('groups requests by organizationId', function() { @@ -293,10 +275,8 @@ describe('Adagio bid adapter', () => { }); 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'); - sandbox.stub(utils, 'generateUUID').returns('blabla'); + sandbox.stub(_internal, 'getDevice').returns({ a: 'a' }); + sandbox.stub(_internal, 'getSite').returns({ domain: 'adagio.io', 'page': 'https://adagio.io/hb' }); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); @@ -310,70 +290,52 @@ describe('Adagio bid adapter', () => { expect(requests[0].data).to.have.all.keys(expectedDataKeys); }); - it('should use a custom generated auctionId and remove transactionId', function() { + it('should use a custom generated auctionId from ortb2.site.ext.data.adg_rtd.uid when available', function() { const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' - sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); const bid01 = new BidRequestBuilder().withParams().build(); - const bidderRequest = new BidderRequestBuilder().build(); + let ortb = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + uid: expectedAuctionId + } + } + } + } + } + } + const bidderRequest = new BidderRequestBuilder(ortb).build(); const requests = spec.buildRequests([bid01], bidderRequest); expect(requests[0].data.adUnits[0].auctionId).eq(expectedAuctionId); expect(requests[0].data.adUnits[0].transactionId).to.not.exist; }); - it('should enrich prebid bid requests params', function() { + it('should use a custom generated auctionId when ortb2.site.ext.data.adg_rtd.uid is absent and remove transactionId', function() { const expectedAuctionId = '373bcda7-9794-4f1c-be2c-0d223d11d579' - const expectedPageviewId = '56befc26-8cf0-472d-b105-73896df8eb89'; sandbox.stub(utils, 'generateUUID').returns(expectedAuctionId); - sandbox.stub(adagio, 'getPageviewId').returns(expectedPageviewId); const bid01 = new BidRequestBuilder().withParams().build(); const bidderRequest = new BidderRequestBuilder().build(); - spec.buildRequests([bid01], bidderRequest); - - expect(bid01.params.adagioAuctionId).eq(expectedAuctionId); - expect(bid01.params.pageviewId).eq(expectedPageviewId); + const requests = spec.buildRequests([bid01], bidderRequest); + expect(requests[0].data.adUnits[0].auctionId).eq(expectedAuctionId); + expect(requests[0].data.adUnits[0].transactionId).to.not.exist; }); - it('should enqueue computed features for collect usage', function() { - sandbox.stub(Date, 'now').returns(12345); + it('should enrich prebid bid requests params', function() { + const expectedPageviewId = '56befc26-8cf0-472d-b105-73896df8eb89'; + sandbox.stub(_internal, 'getAdagioNs').returns({ pageviewId: expectedPageviewId }); 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 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); + 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; + expect(bid01.params.pageviewId).eq(expectedPageviewId); }); it('should force split keyword param into a string', function() { @@ -446,10 +408,102 @@ describe('Adagio bid adapter', () => { expect(requests[0].data.adUnits[3].params.dataLayer).to.not.exist; }); + describe('with adagioRtdProvider enrichments', function() { + const adUnitRtdEnrichments = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + features: { + page_dimensions: '1024x768', + viewport_dimensions: '1024x768', + user_timestamp: '111111111', + dom_loading: '111111111', + } + } + }}} + }, + ortb2Imp: { + ext: { + data: { + adg_rtd: { + adunit_position: '1x1' + } + } + } + } + } + const rtdEnrichments = { + ortb2: { + site: { + ext: { + data: { + adg_rtd: { + session: { + new: true, + rnd: 0.0666 + }, + } + } + } + } + } + } + + it('should add features and data to the request if exists', function() { + const bid01 = new BidRequestBuilder(adUnitRtdEnrichments).withParams().build(); + const bidderRequest = new BidderRequestBuilder(rtdEnrichments).build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.data).to.deep.equal({ + session: { + new: true, + rnd: 0.0666 + } + }); + + expect(requests[0].data.adUnits[0].features).to.deep.equal({ + page_dimensions: '1024x768', + viewport_dimensions: '1024x768', + user_timestamp: '111111111', + dom_loading: '111111111', + adunit_position: '1x1', + print_number: '1' + }) + }); + + it('should add an only "print_number" in features object if ortb2 is not properly defined', function() { + const bid01 = new BidRequestBuilder({ + ortb2: {}, + bidderRequestsCount: 2 + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.adUnits[0].features).to.deep.equal({ + print_number: '2' + }); + }); + + it('should send data.session with default if the ortb2 ext is not properly defined', function() { + const bid01 = new BidRequestBuilder().withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + sandbox.stub(Math, 'random').returns(0.444); + + const requests = spec.buildRequests([bid01], bidderRequest); + + expect(requests[0].data.data.session).to.exist; + expect(requests[0].data.data.session.new).to.equal(true); + expect(requests[0].data.data.session.rnd).to.equal(0.444); + }); + }); + 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'); + it('should set playerName = "other" if user does not set renderer.backupOnly: true', function() { const bid01 = new BidRequestBuilder({ adUnitCode: 'adunit-code-01', mediaTypes: { @@ -468,7 +522,39 @@ describe('Adagio bid adapter', () => { const request = spec.buildRequests([bid01], bidderRequest)[0]; 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 set playerName = "adagio" if user does not set a renderer or set `renderer.backupOnly: true`', function() { + const bid01 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-01', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + } + }, + }).withParams().build(); + const bid02 = new BidRequestBuilder({ + adUnitCode: 'adunit-code-02', + mediaTypes: { + banner: { sizes: [[300, 250]] }, + video: { + context: 'outstream', + playerSize: [[300, 250]], + renderer: { + url: 'https://url.tld', + render: () => true, + backupOnly: true + } + } + }, + }).withParams().build(); + const bidderRequest = new BidderRequestBuilder().build(); + const request = spec.buildRequests([bid01, bid02], bidderRequest)[0]; + + expect(request.data.adUnits[0].mediaTypes.video.playerName).to.equal('adagio'); + expect(request.data.adUnits[1].mediaTypes.video.playerName).to.equal('adagio'); }); }); @@ -517,7 +603,6 @@ describe('Adagio bid adapter', () => { const requests = spec.buildRequests([bid01], bidderRequest); expect(requests).to.have.lengthOf(1); expect(requests[0].data.adUnits[0].mediaTypes.video).to.deep.equal(expected); - sinon.assert.calledTwice(utils.logWarn.withArgs(sinon.match(new RegExp(/^Adagio: The OpenRTB/)))); }); }); @@ -715,77 +800,24 @@ describe('Adagio bid adapter', () => { describe('with GPP', function() { const bid01 = new BidRequestBuilder().withParams().build(); - const regsGpp = 'regs_gpp_consent_string'; - const regsApplicableSections = [2]; - - const ortb2Gpp = 'ortb2_gpp_consent_string'; - const ortb2GppSid = [1]; - - context('When GPP in regs module', function() { - it('send gpp and gppSid to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - applicableSections: regsApplicableSections, - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(regsGpp); - expect(requests[0].data.regs.gppSid).to.equal(regsApplicableSections); - }); - }); - - context('When GPP partially defined in regs module', function() { - it('send gpp and gppSid coming from ortb2 to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - }, - ortb2: { - regs: { - gpp: ortb2Gpp, - gpp_sid: ortb2GppSid, - } - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(ortb2Gpp); - expect(requests[0].data.regs.gppSid).to.equal(ortb2GppSid); - }); - - it('send empty gpp and gppSid if no ortb2 fields to the server', function() { - const bidderRequest = new BidderRequestBuilder({ - gppConsent: { - gppString: regsGpp, - } - }).build(); - - const requests = spec.buildRequests([bid01], bidderRequest); - - expect(requests[0].data.regs.gpp).to.equal(''); - expect(requests[0].data.regs.gppSid).to.be.empty; - }); - }); + const gpp = 'gpp_consent_string'; + const gppSid = [1]; - context('When GPP defined in ortb2 module', function() { + context('When GPP is defined', function() { it('send gpp and gppSid coming from ortb2 to the server', function() { const bidderRequest = new BidderRequestBuilder({ ortb2: { regs: { - gpp: ortb2Gpp, - gpp_sid: ortb2GppSid, + gpp, + gpp_sid: gppSid, } } }).build(); const requests = spec.buildRequests([bid01], bidderRequest); - expect(requests[0].data.regs.gpp).to.equal(ortb2Gpp); - expect(requests[0].data.regs.gppSid).to.equal(ortb2GppSid); + expect(requests[0].data.regs.gpp).to.equal(gpp); + expect(requests[0].data.regs.gppSid).to.equal(gppSid); }); }); @@ -1143,25 +1175,24 @@ describe('Adagio bid adapter', () => { it('should populate ADAGIO queue with ssp-data', function() { sandbox.stub(Date, 'now').returns(12345); + sandbox.stub(_internal, 'hasRtd').returns(true); + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') - adagioMock.expects('enqueue').withExactArgs({ + spec.interpretResponse(serverResponse, bidRequest); + + expect(spy.withArgs({ action: 'ssp-data', ts: 12345, data: serverResponse.body.data - }).once(); - - spec.interpretResponse(serverResponse, bidRequest); - - adagioMock.verify(); + }).calledOnce).to.be.true; }); it('should properly try-catch an exception and return an empty array', function() { - sandbox.stub(adagio, 'enqueue').throws(); - utilsMock.expects('logError').once(); - + sandbox.stub(_internal, 'hasRtd').returns(true); + sandbox.stub(_internal, 'getAdagioNs').returns({ queue: () => { throw new Error('test') } }); + const spy = sandbox.spy(utils, 'logError'); expect(spec.interpretResponse(serverResponse, bidRequest)).to.be.an('array').length(0); - - utilsMock.verify(); + expect(spy.calledOnce).to.be.true; }); describe('Response with video outstream', function() { @@ -1465,188 +1496,6 @@ describe('Adagio bid adapter', () => { }); }); - 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'); - }); - - it('should return `adunit_position` feature when the slot is hidden with value 0x0', function () { - const elem = fixtures.getElementById('0', '0', '0', '0'); - sandbox.stub(window.top.document, 'getElementById').returns(elem); - sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'none' }); - 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.equal('0x0'); - }); - }); - - 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('site information using refererDetection or window.top', function() { it('should returns domain, page and window.referrer in a window.top context', function() { const bidderRequest = new BidderRequestBuilder({ @@ -1660,7 +1509,7 @@ describe('Adagio bid adapter', () => { } }).build(); - expect(adagio.getSite(bidderRequest)).to.deep.equal({ + expect(_internal.getSite(bidderRequest)).to.deep.equal({ domain: 'test.io', page: 'https://test.io/article/a.html', referrer: 'https://google.com', @@ -1669,7 +1518,7 @@ describe('Adagio bid adapter', () => { }); it('should returns domain and page in a cross-domain w/ top domain reached context', function() { - sandbox.stub(utils, 'getWindowTop').throws(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); sandbox.stub(utils, 'getWindowSelf').returns({ document: { referrer: 'https://google.com' @@ -1695,7 +1544,7 @@ describe('Adagio bid adapter', () => { refererInfo: info }).build(); - expect(adagio.getSite(bidderRequest)).to.deep.equal({ + expect(_internal.getSite(bidderRequest)).to.deep.equal({ domain: 'level.io', page: 'http://level.io/', referrer: 'https://google.com', @@ -1704,7 +1553,7 @@ describe('Adagio bid adapter', () => { }); it('should return info in a cross-domain w/o top domain reached and w/o ancestor context', function() { - sandbox.stub(utils, 'getWindowTop').throws(); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); const info = { numIframes: 2, @@ -1725,119 +1574,11 @@ describe('Adagio bid adapter', () => { refererInfo: info }).build(); - const s = adagio.getSite(bidderRequest) + const s = _internal.getSite(bidderRequest) expect(s.domain).equal('example.com') expect(s.page).equal('http://example.com/iframe1.html') expect(s.referrer).match(/^https?:\/\/.+/); expect(s.top).equal(false) }); }); - - 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('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)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.equals('// hash: ' + VALID_HASH + '\n' + VALID_SCRIPT_CONTENT); - utilsMock.verify(); - }); - - 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('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)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - 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('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)); - - expect(localStorage.getItem(ADAGIO_LOCALSTORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify missing hash', function () { - localStorage.setItem(ADAGIO_LOCALSTORAGE_KEY, VALID_SCRIPT_CONTENT); - - 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/adagioRtdProvider_spec.js b/test/spec/modules/adagioRtdProvider_spec.js new file mode 100644 index 00000000000..64c006fbc3f --- /dev/null +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -0,0 +1,682 @@ +import { + PLACEMENT_SOURCES, + _internal, + adagioRtdSubmodule, + storage, +} from 'modules/adagioRtdProvider.js'; +import * as utils from 'src/utils.js'; +import { loadExternalScript } from '../../../src/adloader.js'; +import { expect } from 'chai'; +import { getGlobal } from '../../../src/prebidGlobal.js'; + +describe('Adagio Rtd Provider', function () { + const SUBMODULE_NAME = 'adagio'; + + function getElementByIdMock(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 + }; + } + }; + } + + let sandbox; + let clock; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + clock = sandbox.useFakeTimers(); + }); + + afterEach(function () { + clock.restore(); + sandbox.restore(); + }); + + describe('submodule `init`', function () { + const config = { + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 'mysite' + } + }; + + it('exists', function () { + expect(adagioRtdSubmodule.init).to.be.a('function'); + }); + + it('returns false missing config params', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + }); + expect(value).to.equal(false); + }); + + it('returns false if missing providers param', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: {} + }); + expect(value).to.equal(false); + }); + + it('returns false if organizationId param is not a string', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: { + organizationId: 1000, + site: 'mysite' + } + }); + expect(value).to.equal(false); + }); + + it('returns false if `site` param is not a string', function () { + const value = adagioRtdSubmodule.init({ + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 123 + } + }); + expect(value).to.equal(false); + }); + + it('returns true if `organizationId` and `site` params included', function () { + const value = adagioRtdSubmodule.init(config); + expect(value).to.equal(true); + }); + + it('load an external script if localStorageIsEnabled is enabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, true) + adagioRtdSubmodule.init(config); + expect(loadExternalScript.called).to.be.true; + }); + + it('do not load an external script if localStorageIsEnabled is disabled', function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsArgWith(0, false) + adagioRtdSubmodule.init(config); + expect(loadExternalScript.called).to.be.false; + }); + + describe('store session data in localStorage', function () { + const session = { + lastActivityTime: 1714116520700, + id: 'uid-1234', + rnd: 0.5697, + vwSmplg: 0.1, + vwSmplgNxt: 0.1, + pages: 1 + }; + + it('store new session data for further usage', function () { + const storageValue = null; + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-1234'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + new: true, + id: utils.generateUUID(), + rnd: Math.random(), + pages: 1, + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('store existing session data for further usage', function () { + const storageValue = JSON.stringify({session: session}); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...session, + new: false, + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('store new session if old session has expired data for further usage', function () { + const storageValue = JSON.stringify({session: session}); + sandbox.stub(Date, 'now').returns(1715679344351); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-5678'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...session, + new: true, + id: utils.generateUUID(), + rnd: Math.random(), + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); + + describe('store session data in localStorage when used with external AB Test snippet', function () { + const sessionWithABTest = { + lastActivityTime: 1714116520700, + id: 'uid-1234', + rnd: 0.5697, + vwSmplg: 0.1, + vwSmplgNxt: 0.1, + testName: 'adg-test', + testVersion: 'srv', + initiator: 'snippet', + pages: 1 + }; + + it('store new session data instancied by the AB Test snippet for further usage', function () { + const sessionWithNewFlag = { ...sessionWithABTest, new: true }; + const storageValue = JSON.stringify({session: sessionWithNewFlag}); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...sessionWithNewFlag + } + } + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + + it('store new session data after removing AB Test props when initiator is not the snippet', function () { + const sessionWithNewFlag = { ...sessionWithABTest, new: false, initiator: 'adgjs' }; + const storageValue = JSON.stringify({session: sessionWithNewFlag}); + sandbox.stub(storage, 'getDataFromLocalStorage').callsArgWith(1, storageValue); + sandbox.stub(Date, 'now').returns(1714116520710); + sandbox.stub(Math, 'random').returns(0.8); + sandbox.stub(utils, 'generateUUID').returns('uid-5678'); + + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + + adagioRtdSubmodule.init(config); + + const expected = { + session: { + ...sessionWithNewFlag, + new: true, + id: utils.generateUUID(), + rnd: Math.random(), + } + } + delete expected.session.testName; + delete expected.session.testVersion; + + expect(spy.withArgs({ + action: 'session', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); + }); + + describe('submodule `getBidRequestData`', function () { + const bidReqConfig = { + 'timeout': 700, + 'adUnits': [ + { + 'code': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]] + } + }, + 'ortb2Imp': {}, + 'bids': [ + { + 'bidder': 'adagio', + 'params': { + 'organizationId': '1004', + 'site': 'maville', + 'useAdUnitCodeAsPlacement': true, + 'adUnitElementId': 'div-gpt-ad-1460505748561-0', + 'pagetype': 'article', + } + }, + { + 'bidder': 'another', + 'params': { + 'pubid': 'xxx', + } + } + ] + } + ], + 'adUnitCodes': [ + 'div-gpt-ad-1460505748561-0' + ], + 'ortb2Fragments': { + 'global': { + 'regs': { + 'ext': { + 'gdpr': 1 + } + }, + 'site': { + 'domain': 'example.com', + 'publisher': { + 'domain': 'example.com' + }, + 'page': 'http://example.com/page.html', + }, + 'device': { + 'w': 1359, + 'h': 1253, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'fr' + } + }, + 'bidder': {} + } + }; + + function cb() {} + + beforeEach(function() { + _internal.getFeatures().reset(); + }); + + it('exists', function () { + expect(adagioRtdSubmodule.getBidRequestData).to.be.a('function'); + }); + + it('update the ortb2Fragments object with adg_rtd signals', function() { + const bidRequest = utils.deepClone(bidReqConfig); + + sandbox.stub(window.top.document, 'getElementById').returns(getElementByIdMock()); + sandbox.stub(window.top, 'getComputedStyle').returns({ display: 'block' }); + sandbox.stub(utils, 'inIframe').returns(false); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + const signals = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(signals).to.have.property('features'); + expect(signals).to.have.property('session'); + expect(signals).to.have.property('uid'); + expect(signals.features.viewport_dimensions).to.match(/\d+x\d+/); + expect(signals.features.page_dimensions).to.match(/\d+x\d+/); + + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd.adunit_position).to.match(/\d+x\d+/); + }); + + describe('update the ortb2Fragments object a SafeFrame context', function() { + it('update', function() { + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + window.$sf = { + ext: { + geom() { + return { + 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 = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal('1920x1177'); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal('210x859'); + + window.$sf = undefined; + }); + + it('handle missformated $sf object and update', function() { + sandbox.stub(utils, 'isSafeFrameWindow').returns(true); + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + + window.$sf = { + ext: { + geom: '' + } + }; + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal(''); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + + window.$sf = undefined; + }); + }); + + describe('update the ortb2Fragments object in a "inIframe" context', function() { + it('update when window.top is accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(utils, 'inIframe').returns(true); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + + it('catch error when window.top is accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(true); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + sandbox.stub(window.document, 'getElementById').throws(); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + }); + + it('update the ortb2Fragments object when window.top is not accessible', function() { + sandbox.stub(utils, 'canAccessWindowTop').returns(false); + sandbox.stub(utils, 'isSafeFrameWindow').returns(false); + + const bidRequest = utils.deepClone(bidReqConfig); + adagioRtdSubmodule.getBidRequestData(bidRequest, cb); + + const fragmentExt = bidRequest.ortb2Fragments.global.site.ext.data.adg_rtd; + expect(fragmentExt.features.viewport_dimensions).equal(''); + expect(fragmentExt.features.page_dimensions).equal(''); + + const ortb2ImpExt = bidRequest.adUnits[0].ortb2Imp.ext.data.adg_rtd; + expect(ortb2ImpExt.adunit_position).equal(''); + }); + + describe('update the ortb2Imp.ext.data.placement if not present', function() { + const config = { + name: SUBMODULE_NAME, + params: { + organizationId: '1000', + site: 'mysite' + } + }; + + it('update the placement value with the adUnit.code value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; + + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + }); + + it('update the placement value with the gpid value', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.GPID; + + const bidRequest = utils.deepClone(bidReqConfig); + const gpid = '/19968336/header-bid-tag-0' + utils.deepSetValue(bidRequest.adUnits[0], 'ortb2Imp.ext.gpid', gpid) + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(gpid); + }); + + it('update the placement value the legacy adUnit[].bids adagio.params.placement value', function() { + const placement = 'placement-value'; + + const configCopy = utils.deepClone(config); + + const bidRequest = utils.deepClone(bidReqConfig); + bidRequest.adUnits[0].bids[0].params.placement = placement; + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal(placement); + }); + + it('it does not populate `ortb2Imp.ext.data.placement` if no fallback', function() { + const configCopy = utils.deepClone(config); + const bidRequest = utils.deepClone(bidReqConfig); + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.not.exist; + }); + + it('ensure we create the `ortb2Imp` object if it does not exist', function() { + const configCopy = utils.deepClone(config); + configCopy.params.placementSource = PLACEMENT_SOURCES.ADUNITCODE; + + const bidRequest = utils.deepClone(bidReqConfig); + delete bidRequest.adUnits[0].ortb2Imp; + + adagioRtdSubmodule.getBidRequestData(bidRequest, cb, configCopy); + expect(bidRequest.adUnits[0]).to.have.property('ortb2Imp'); + expect(bidRequest.adUnits[0].ortb2Imp.ext.data.placement).to.equal('div-gpt-ad-1460505748561-0'); + }); + }); + }); + + describe('submodule `onBidRequestEvent`', function() { + const bidderRequest = { + 'bidderCode': 'adagio', + 'auctionId': '3de10dc0-fe75-480f-95cc-f15f2c4929fe', + 'bidderRequestId': '4ecd1f17cf829b', + 'bids': [ + { + 'bidder': 'adagio', + 'params': { + 'organizationId': '1000', + 'site': 'example', + 'adUnitElementId': 'div-gpt-ad-1460505748561-0', + 'pagetype': 'article', + 'environment': 'desktop', + 'placement': 'div-gpt-ad-1460505748561-0', + 'adagioAuctionId': '4c259968-0158-443d-af93-551bac594b6c', + 'pageviewId': 'dfb9b067-e5c4-4212-97bb-c67d6313ecaf' + }, + 'ortb2Imp': { + 'ext': { + 'tid': '235c991e-fcc4-416b-95d3-f60e53575bee', + 'data': { + 'adserver': { + 'name': 'gam', + 'adslot': '/19968336/header-bid-tag-0' + }, + 'pbadslot': '/19968336/header-bid-tag-0', + 'adunit_position': '8x95' + }, + 'gpid': '/19968336/header-bid-tag-0' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '235c991e-fcc4-416b-95d3-f60e53575bee', + 'adUnitId': '79ab5904-0b21-4235-965a-f4905af072b7', + 'bidId': '534aa529a44e0e', + 'bidderRequestId': '4ecd1f17cf829b', + 'auctionId': '3de10dc0-fe75-480f-95cc-f15f2c4929fe', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + 'uid': 'dfb9b067-e5c4-4212-97bb-c67d6313ecaf', + 'features': { + 'page_dimensions': '1359x1353', + 'viewport_dimensions': '1359x1253', + 'user_timestamp': '1715621032', + 'dom_loading': '28' + }, + 'session': { + 'new': true, + 'rnd': 0.020644826280300954, + 'vwSmplg': 0.1, + 'vwSmplgNxt': 0.1, + 'pages': 1 + } + } + } + } + } + }, + }, + ], + 'auctionStart': 1715613832791, + 'timeout': 700, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + 'features': { + 'page_dimensions': '1359x1353', + 'viewport_dimensions': '1359x1253', + 'user_timestamp': '1715621032', + 'dom_loading': '28' + }, + 'session': { + 'new': true, + 'rnd': 0.020644826280300954, + 'vwSmplg': 0.1, + 'vwSmplgNxt': 0.1, + 'pages': 1 + } + } + } + } + } + }, + 'start': 1715613832796 + } + + it('store a copy of computed property', function() { + const spy = sandbox.spy(_internal.getAdagioNs().queue, 'push') + sandbox.stub(Date, 'now').returns(12345); + + _internal.getGuard().clear(); + + const config = { + params: { + organizationId: '1000', + site: 'example' + } + }; + const bidderRequestCopy = utils.deepClone(bidderRequest); + adagioRtdSubmodule.onBidRequestEvent(bidderRequestCopy, config); + + clock.tick(1); + + const { + bidder, + adUnitCode, + mediaTypes, + params, + auctionId, + bidderRequestsCount } = bidderRequestCopy.bids[0]; + + const expected = { + bidder, + adUnitCode, + mediaTypes, + ortb2: bidderRequestCopy.bids[0].ortb2.site.ext.data, + ortb2Imp: bidderRequestCopy.bids[0].ortb2Imp.ext.data, + params, + auctionId, + bidderRequestsCount, + organizationId: config.params.organizationId, + site: config.params.site, + localPbjs: 'pbjs', + localPbjsRef: getGlobal() + } + + expect(spy.withArgs({ + action: 'store', + ts: Date.now(), + data: expected, + }).calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/adbookpspBidAdapter_spec.js b/test/spec/modules/adbookpspBidAdapter_spec.js deleted file mode 100755 index 3f26cd7749f..00000000000 --- a/test/spec/modules/adbookpspBidAdapter_spec.js +++ /dev/null @@ -1,1344 +0,0 @@ -import { expect } from 'chai'; -import * as utils from '../../../src/utils.js'; -import { - spec, - storage, - DEFAULT_BIDDER_CONFIG, - VERSION, - common, -} from '../../../modules/adbookpspBidAdapter.js'; - -describe('adbookpsp bid adapter', () => { - let sandbox; - - beforeEach(function () { - sandbox = sinon.sandbox.create(); - - sandbox - .stub(common, 'generateUUID') - .returns('54444444-5444-4444-9444-544444444444'); - sandbox.stub(common, 'getWindowDimensions').returns({ - innerWidth: 100, - innerHeight: 100, - }); - }); - - afterEach(function () { - sandbox.restore(); - }); - - describe('isBidRequestValid()', () => { - it('should return false when there is no banner in mediaTypes', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when orgId and placementId is not defined', () => { - const bid = utils.deepClone(bannerBid); - delete bid.params.placementId; - delete bid.params.orgId; - - expect(spec.isBidRequestValid(bid)).to.be.false; - }); - - it('should return true when orgId is set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - delete bid.params.orgId; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - expect(spec.isBidRequestValid(bid)).to.be.true; - }); - - it('should return true when required params found', () => { - expect(spec.isBidRequestValid(bannerBid)).to.equal(true); - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - expect(spec.isBidRequestValid(mixedBid)).to.equal(true); - }); - - it('should return false when sizes for banner are not specified', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when sizes for banner are invalid', () => { - const bid = utils.deepClone(bannerBid); - delete bid.mediaTypes.banner.sizes; - - bid.mediaTypes.banner.sizes = [['123', 'foo']]; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return true if player size is set via playerSize', () => { - expect(spec.isBidRequestValid(videoBid)).to.equal(true); - }); - - it('should return true if player size is set via w and h', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should reutrn false if player size is not set', () => { - const bid = utils.deepClone(videoBid); - delete bid.mediaTypes.video.playerSize; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests()', () => { - it('should build correct request for banner bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.equal(bannerExchangeRequest); - }); - - it('should build correct request for video bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(DEFAULT_BIDDER_CONFIG) - .withArgs('adbookpsp.exchangeUrl') - .returns(DEFAULT_BIDDER_CONFIG.exchangeUrl) - .withArgs('adbookpsp.orgId') - .returns(undefined); - - const requests = spec.buildRequests([videoBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include({ - ...videoExchangeRequest, - ext: { - adbook: { - config: DEFAULT_BIDDER_CONFIG, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, - }); - }); - - it('should build correct request for video bid with w and h', () => { - const bid = utils.deepClone(videoBid); - - delete bid.mediaTypes.video.playerSize; - - bid.mediaTypes.video.w = 400; - bid.mediaTypes.video.h = 300; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(400); - expect(requestData.imp[0].video.h).to.equal(300); - }); - - it('should build correct request for video bid with both w, h and playerSize', () => { - const bid = utils.deepClone(videoBid); - - bid.mediaTypes.video.w = 640; - bid.mediaTypes.video.h = 480; - - const [request] = spec.buildRequests([bid], bidderRequest); - const requestData = JSON.parse(request.data); - - expect(requestData.imp[0].video.w).to.equal(640); - expect(requestData.imp[0].video.h).to.equal(480); - }); - - it('should build correct request for mixed bid', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns(undefined) - .withArgs('adbookpsp.exchangeUrl') - .returns('https://ex.fattail.com/openrtb2'); - - const requests = spec.buildRequests([mixedBid], bidderRequest); - - expect(requests).to.have.lengthOf(1); - expect(requests[0]).to.deep.include({ - method: 'POST', - url: 'https://ex.fattail.com/openrtb2', - options: { - contentType: 'application/json', - withCredentials: true, - }, - }); - expect(JSON.parse(requests[0].data)).to.deep.include( - mixedExchangeRequest - ); - }); - - it('should use orgId from config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('129576'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: '129576', - }, - }); - }); - - it('should use orgId from adUnit when orgId is also set in config', () => { - const bid = utils.deepClone(bannerBid); - - delete bid.params.placementId; - - bid.params.orgId = 'adUnitOrgId'; - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.orgId') - .returns('configOrgId'); - - const requests = spec.buildRequests([bid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0].ext).to.deep.include({ - adbook: { - orgId: 'adUnitOrgId', - }, - }); - }); - - it('should include in request GDPR options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - }, - }, - }); - }); - - it('should include in request USP (CPPA) options if available', () => { - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 0, - ext: { - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should pass valid coppa flag based on config', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const request = utils.deepClone(bidderRequest); - - delete request.gdprConsent; - delete request.uspConsent; - - const requests = spec.buildRequests([bannerBid, mixedBid], request); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - }, - }); - }); - - it('should pass GDPR, USP (CCPA) and COPPA options', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - - expect(JSON.parse(requests[0].data)).to.deep.include({ - regs: { - coppa: 1, - ext: { - gdpr: 1, - gdprConsentString: 'gdprConsentString', - us_privacy: 'uspConsentString', - }, - }, - }); - }); - - it('should generate and pass user id when is not present in cookie and local storage is not enabled', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass user id when is present in cookie', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(false); - sandbox - .stub(storage, 'getCookie') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should pass user id if is present in local storage', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox - .stub(storage, 'getDataFromLocalStorage') - .returns('e35da6bb-f2f8-443b-aeff-3375bef45c9d'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.equal( - 'e35da6bb-f2f8-443b-aeff-3375bef45c9d' - ); - }); - - it('should regenerate user id if it is invalid', () => { - sandbox.stub(storage, 'localStorageIsEnabled').returns(true); - sandbox.stub(storage, 'getDataFromLocalStorage').returns('foo'); - - const requests = spec.buildRequests([bannerBid, mixedBid], bidderRequest); - const rtbRequest = JSON.parse(requests[0].data); - expect(rtbRequest.user.id).to.have.lengthOf(36); - }); - - it('should pass schain if available', () => { - const bid = utils.deepClone(bannerBid); - const schain = { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'exchange1.com', - sid: '1234', - hp: 1, - rid: 'bid-request-1', - name: 'publisher', - domain: 'publisher.com', - }, - ], - }; - - bid.schain = schain; - - const requests = spec.buildRequests([bid], bidderRequest); - - expect(JSON.parse(requests[0].data).source).to.deep.include({ - ext: { - schain, - }, - }); - }); - - it('return empty array if there are no valid bid requests', () => { - const requests = spec.buildRequests([], bidderRequest); - - expect(requests).to.deep.equal([]); - }); - - it('should prioritize device information set in config', () => { - const ua = - 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_5_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.1.1 Mobile/15E148 Safari/604.1'; - - sandbox.stub(common, 'getConfig').withArgs('device').returns({ - ua, - }); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - - expect(JSON.parse(requests[0].data).device.ua).to.equal(ua); - }); - - it('should include bidder config', () => { - const bidderConfig = { - bidTTL: 500, - defaultCurrency: 'USD', - exchangeUrl: 'https://exsb.fattail.com/openrtb2', - winTrackingEnabled: true, - winTrackingUrl: 'https://evsb.fattail.com/wins', - orgId: '129576', - }; - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp') - .returns(bidderConfig); - - const requests = spec.buildRequests([bannerBid], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.ext).to.deep.include({ - adbook: { - config: bidderConfig, - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }); - }); - - it('should use bidder video params if they are set', () => { - const videoBidWithParams = utils.deepClone(videoBid); - const bidderVideoParams = { - api: [1, 2], - mimes: ['video/mp4', 'video/x-flv'], - playbackmethod: [3, 4], - protocols: [5, 6], - minduration: 10, - maxduration: 30, - }; - videoBidWithParams.params.video = bidderVideoParams; - - const requests = spec.buildRequests([videoBidWithParams], bidderRequest); - const request = JSON.parse(requests[0].data); - - expect(request.imp[0]).to.deep.include({ - video: { - ...bidderVideoParams, - w: videoBidWithParams.mediaTypes.video.playerSize[0][0], - h: videoBidWithParams.mediaTypes.video.playerSize[0][1], - }, - }); - }); - }); - - describe('interpretResponse()', () => { - it('should correctly interpret valid response', () => { - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.defaultCurrency') - .returns(DEFAULT_BIDDER_CONFIG.defaultCurrency) - .withArgs('adbookpsp.bidTTL') - .returns(DEFAULT_BIDDER_CONFIG.bidTTL); - - const response = utils.deepClone(exchangeResponse); - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([ - { - bidderRequestId: '999ccceeee11', - requestId: '9873kfse', - bidId: 'bid123456', - width: 300, - height: 250, - ttl: 300, - cpm: 0.5, - currency: 'USD', - creativeId: '123456789', - mediaType: 'banner', - meta: { - advertiserDomains: ['advertiser.com'], - mediaType: 'banner', - primaryCatId: 'IAB2-1', - secondaryCatIds: ['IAB2-2', 'IAB2-3'], - }, - netRevenue: true, - nurl: 'http://win.example.url', - adUnitCode: 'div-gpt-ad-837465923534-0', - ad: '
ad
', - adId: '5', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', // the value to the left of the underscore represents the index of the ad id and the number to the right represents the order index - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }, - referrer: 'http://prebid-test-page.io:8080/banner.html', - lineItemId: '2342345', - }, - { - ad: '', - adId: '10', - adUnitCode: 'div-gpt-ad-837465923534-0', - adserverTargeting: { - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }, - bidId: 'bid4321', - bidderRequestId: '999ccceeee11', - cpm: 0.45, - creativeId: '543123', - currency: 'USD', - height: 250, - lineItemId: '2121221', - mediaType: 'video', - meta: { - advertiserDomains: ['advertiser.com', 'campaign.advertiser.com'], - mediaType: 'video', - primaryCatId: 'IAB2-3', - secondaryCatIds: [], - }, - netRevenue: true, - nurl: 'http://win.example.url', - referrer: 'http://prebid-test-page.io:8080/banner.html', - requestId: '120kfeske', - ttl: 300, - vastXml: - '', - width: 300, - }, - ]); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for multiple impressions', () => { - const response = utils.deepClone(exchangeResponse); - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '5', - hb_deal_adbookpsp: 'werwetwerw', - hb_liid_adbookpsp: '2342345', - hb_ordid_adbookpsp: '567843', - }); - expect(bids[1].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should place valid GAM targeting for all bids when multiple bids are present for single impression', () => { - const response = utils.deepClone(exchangeResponse); - - response.seatbid[1].bid[0].impid = '9873kfse'; - - const bids = spec.interpretResponse( - { body: response }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.have.length(2); - for (const bid of bids) { - expect(bid.adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0,1_0', - hb_adid_c_adbookpsp: '5,10', - hb_deal_adbookpsp: 'werwetwerw,dsfxcxcvxc', - hb_liid_adbookpsp: '2342345,2121221', - hb_ordid_adbookpsp: '567843,5678234', - }); - } - }); - - it('should return no bids if response id does not match bidderRequestId', () => { - const body = utils.deepClone(exchangeResponse); - body.id = '999'; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include seatbid', () => { - const body = utils.deepClone(exchangeResponse); - delete body.seatbid; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should return no bids if response does not include any bids', () => { - const body = utils.deepClone(exchangeResponse); - body.seatbid = []; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid video bids', () => { - const body = utils.deepClone(exchangeResponse); - - body.seatbid.shift(); - body.seatbid[0].bid[0].adid = 34; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should exclude invalid banner bids', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid.pop(); - - delete body.seatbid[0].bid[0].w; - delete body.seatbid[0].bid[0].h; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids).to.deep.equal([]); - }); - - it('should not include invalid banner bids in targeting map', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - body.seatbid[0].bid[0].h = '600'; - - request.imp[0].banner.format.push({ w: 300, h: 600 }); - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(exchangeBidRequest) } - ); - - expect(bids[0].adserverTargeting).to.deep.equal({ - hb_ad_ord_adbookpsp: '0_0', - hb_adid_c_adbookpsp: '10', - hb_deal_adbookpsp: 'dsfxcxcvxc', - hb_liid_adbookpsp: '2121221', - hb_ordid_adbookpsp: '5678234', - }); - }); - - it('should not validate banner bid dimensions if bid request has single size', () => { - const body = utils.deepClone(exchangeResponse); - const request = utils.deepClone(exchangeBidRequest); - - delete body.seatbid[1]; - delete body.seatbid[0].bid[0].h; - delete body.seatbid[0].bid[0].w; - - const bids = spec.interpretResponse( - { body }, - { data: JSON.stringify(request) } - ); - - expect(bids.length).to.equal(1); - }); - }); - - describe('getUserSyncs()', () => { - it('should return user syncs if there are included in the response and syncs are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should not return user syncs if syncs are disabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([]); - }); - - it('should return image syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should return iframe syncs if they are enabled', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: exchangeResponse }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ]); - }); - - it('should append COPPA status to sync url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }] - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?coppa=1', - }, - ]); - }); - - it('should append GDPR consent data to url', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(false); - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' } - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString', - }, - ]); - }); - - it('should append USP (CCPA) consent string to url', () => { - const syncs = spec.getUserSyncs( - { - pixelEnabled: false, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - undefined, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?us_privacy=uspConsentString', - }, - ]); - }); - - it('should append COPPA, GDPR and USP (CCPA) url params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: true, - }, - [{ body: utils.deepClone(exchangeResponse) }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567?gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - - it('should respect url param syntax when appending params', () => { - sandbox.stub(common, 'getConfig').withArgs('coppa').returns(true); - - const response = utils.deepClone(exchangeResponse); - - response.ext.sync[0] = { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4', - }; - - const syncs = spec.getUserSyncs( - { - pixelEnabled: true, - iframeEnabled: false, - }, - [{ body: response }], - { gdprApplies: true, consentString: 'gdprConsentString' }, - 'uspConsentString' - ); - - expect(syncs).to.deep.equal([ - { - type: 'image', - url: 'http://sometest.com/sync/1234567?horseCount=4&gdpr=1&consentString=gdprConsentString&us_privacy=uspConsentString&coppa=1', - }, - ]); - }); - }); - - describe('onBidWon()', () => { - it('should track win if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - }); - - expect( - spy.calledWith( - 'https://ev.fattail.com/wins?impId=requestId&reqId=bidderRequestId&bidId=bidId' - ) - ).to.equal(true); - }); - it('should call bid.nurl if win tracking is enabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(true) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.calledWith('http://win.example.url')).to.equal(true); - }); - it('should not track win nor call bid.nurl if win tracking is disabled', () => { - const spy = sandbox.spy(utils, 'triggerPixel'); - - sandbox - .stub(common, 'getConfig') - .withArgs('adbookpsp.winTrackingEnabled') - .returns(false) - .withArgs('adbookpsp.winTrackingUrl') - .returns('https://ev.fattail.com/wins'); - - spec.onBidWon({ - requestId: 'requestId', - bidderRequestId: 'bidderRequestId', - bidId: 'bidId', - nurl: 'http://win.example.url', - }); - - expect(spy.notCalled).to.equal(true); - }); - }); -}); - -const bidderRequest = { - auctionId: 'aaccee333311', - bidderRequestId: '999ccceeee11', - timeout: 200, - refererInfo: { - page: 'http://mock-page.com', - domain: 'mock-page.com', - ref: 'http://example-domain.com/foo', - }, - gdprConsent: { - gdprApplies: 1, - consentString: 'gdprConsentString', - }, - uspConsent: 'uspConsentString', - ortb2: { - source: { - tid: 'aaccee333311' - } - } -}; - -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: 'mock-page.com', - page: 'http://mock-page.com', - 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: 'mock-page.com', - page: 'http://mock-page.com', - 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: 'mock-page.com', - page: 'http://mock-page.com', - ref: 'http://example-domain.com/foo', - }, - source: { - fd: 1, - tid: 'aaccee333311', - }, - tmax: 200, - user: { - gdprConsentString: 'gdprConsentString', - id: '54444444-5444-4444-9444-544444444444', - }, - imp: [ - { - banner: { - format: [ - { - w: 300, - h: 600, - }, - ], - w: 300, - h: 600, - topframe: 0, - pos: 0, - }, - video: { - h: 600, - mimes: ['video/mp4'], - w: 300, - }, - ext: { - adbook: { - orgId: '129576', - }, - }, - id: '120kfeske', - tagid: 'div-gpt-ad-9383743831-5', - }, - ], - ext: { - adbook: { - version: { - adapter: VERSION, - prebid: '$prebid.version$', - }, - }, - }, -}; - -const exchangeBidRequest = { - id: '999ccceeee11', - tmax: 200, - imp: [ - { - id: '9873kfse', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - { - id: '120kfeske', - banner: { - format: [ - { - w: 300, - h: 250, - }, - ], - }, - video: { - w: 300, - h: 250, - }, - tagid: 'div-gpt-ad-837465923534-0', - }, - ], - source: { - fd: 1, - tid: 'aaccee333311', - }, - site: { - domain: location.hostname, - page: location.href, - ref: 'http://prebid-test-page.io:8080/banner.html', - }, -}; - -const exchangeResponse = { - id: '999ccceeee11', - seatbid: [ - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid123456', - w: 300, - h: 250, - impid: '9873kfse', - price: 0.5, - exp: 300, - crid: '123456789', - adm: '
ad
', - adid: '5', - dealid: 'werwetwerw', - nurl: 'http://win.example.url', - ext: { - liid: '2342345', - ordid: '567843', - }, - cat: ['IAB2-1', 'IAB2-2', 'IAB2-3'], - adomain: ['advertiser.com'], - }, - ], - }, - { - seat: 'adbookpsp', - group: 0, - bid: [ - { - id: 'bid4321', - impid: '120kfeske', - price: 0.45, - exp: 300, - crid: '543123', - adm: '', - adid: '10', - dealid: 'dsfxcxcvxc', - nurl: 'http://win.example.url', - ext: { - liid: '2121221', - ordid: '5678234', - }, - cat: ['IAB2-3'], - adomain: ['advertiser.com', 'campaign.advertiser.com'], - }, - ], - }, - ], - ext: { - sync: [ - { - type: 'image', - url: 'http://sometest.com/sync/1234567', - }, - { - type: 'iframe', - url: 'http://sometest.com/sync/1234567', - }, - ], - }, -}; diff --git a/test/spec/modules/adfBidAdapter_spec.js b/test/spec/modules/adfBidAdapter_spec.js index d4c5f5c3c38..6f4395d548f 100644 --- a/test/spec/modules/adfBidAdapter_spec.js +++ b/test/spec/modules/adfBidAdapter_spec.js @@ -321,17 +321,14 @@ describe('Adf adapter', function () { let validBidRequests = [{ bidId: 'bidId', params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) + userIdAsEids: [ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ] }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data); - assert.deepEqual(request.user.ext.eids, [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } - ]); + assert.deepEqual(request.user.ext.eids, validBidRequests[0].userIdAsEids); }); it('should send currency if defined', function () { @@ -429,6 +426,18 @@ describe('Adf adapter', function () { } }); + it('should add first party data', function () { + let validBidRequests = [ + { bidId: 'bidId', params: { mid: 1000 }, mediaTypes: { video: {} }, ortb2Imp: { ext: { data: { some: 'value' } } } }, + { bidId: 'bidId2', params: { mid: 1001 }, mediaTypes: { video: {} }, ortb2Imp: { ext: { data: { some: 'value', another: 1 } } } }, + { bidId: 'bidId3', params: { mid: 1002 }, mediaTypes: { video: {} }, ortb2Imp: { ext: {} } } + ]; + let imps = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data).imp; + for (let i = 0; i < 3; i++) { + assert.deepEqual(imps[i].ext.data, validBidRequests[i].ortb2Imp.ext.data); + } + }); + describe('dynamic placement tag', function () { it('should add imp parameters correctly', function () { const validBidRequests = [ diff --git a/test/spec/modules/adgenerationBidAdapter_spec.js b/test/spec/modules/adgenerationBidAdapter_spec.js index 9a3bf61fe23..7a95d4272fb 100644 --- a/test/spec/modules/adgenerationBidAdapter_spec.js +++ b/test/spec/modules/adgenerationBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('AdgenerationAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/adgridBidAdapter_spec.js b/test/spec/modules/adgridBidAdapter_spec.js new file mode 100644 index 00000000000..0d7ad9c245d --- /dev/null +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -0,0 +1,131 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/adgridBidAdapter.js' + +const globalConfig = { + method: 'POST', + endPoint: 'https://api-prebid.adgrid.io/api/v1/auction' +}; + +describe('AdGrid Bid Adapter', function () { + const bannerRequest = [{ + bidId: 123456, + auctionId: 98765, + mediaTypes: { + banner: { + sizes: [[300, 250]], + } + }, + params: { + domainId: 12345 + } + }]; + + const videoRequest = [{ + bidId: 123456, + auctionId: 98765, + mediaTypes: { + video: { + playerSize: [ + [640, 480] + ], + context: 'instream' + } + }, + params: { + domainId: 12345 + } + }]; + + describe('isBidRequestValid', function () { + it('Should return true when domainId exist inside params object', function () { + const isBidValid = spec.isBidRequestValid(bannerRequest[0]); + expect(isBidValid).to.be.true; + }); + + it('Should return false when domainId is not exist inside params object', function () { + const isBidNotValid = spec.isBidRequestValid(null); + expect(isBidNotValid).to.be.false; + }); + }); + + describe('buildRequests', function () { + const request = spec.buildRequests(bannerRequest, bannerRequest[0]); + const requestVideo = spec.buildRequests(videoRequest, videoRequest[0]); + const payload = request.data; + const apiURL = request.url; + const method = request.method; + + it('Test the request is not empty', function () { + expect(request).to.not.be.empty; + }); + + it('Test the request payload is not empty', function () { + expect(payload).to.not.be.empty; + }); + + it('Test the API End Point', function () { + expect(apiURL).to.equal(globalConfig.endPoint); + }); + + it('should send the correct method', function () { + expect(method).to.equal(globalConfig.method); + }); + + it('should send the correct requestId', function () { + expect(request.data.bids[0].requestId).to.equal(bannerRequest[0].bidId); + expect(requestVideo.data.bids[0].requestId).to.equal(videoRequest[0].bidId); + }); + + it('should send the correct sizes array', function () { + expect(request.data.bids[0].sizes).to.be.an('array'); + }); + + it('should send the correct media type', function () { + expect(request.data.bids[0].mediaType).to.equal('banner') + expect(requestVideo.data.bids[0].mediaType).to.equal('video') + }); + }); + + describe('interpretResponse', function () { + const responseObj = { + bids: [ + { + bidId: '4b99f3428651c1', + cpm: 7.7, + ad: '
Ad Content
', + creativeId: '9004', + currency: 'USD', + mediaType: 'banner', + width: 320, + height: 50, + domainId: '2002', + marketplaceId: '703', + devices: 'desktop' + } + ] + }; + + it('Test the interpretResponse function', function () { + const receivedBid = responseObj.bids[0]; + const response = {}; + response.body = responseObj; + + const bidRequest = {}; + bidRequest.currency = 'USD'; + + const bidResponse = spec.interpretResponse(response, bidRequest); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.requestId).to.equal(receivedBid.bidId); + expect(bid.ad).to.equal(receivedBid.ad); + expect(bid.cpm).to.equal(receivedBid.cpm); + expect(bid.mediaType).to.equal(receivedBid.mediaType); + expect(bid.creativeId).to.equal(receivedBid.creativeId); + expect(bid.width).to.equal(receivedBid.width); + expect(bid.height).to.equal(receivedBid.height); + expect(bid.currency).to.equal(receivedBid.currency); + }); + }); +}); diff --git a/test/spec/modules/adhashBidAdapter_spec.js b/test/spec/modules/adhashBidAdapter_spec.js index cc643d6d2ab..f3b63a2359b 100644 --- a/test/spec/modules/adhashBidAdapter_spec.js +++ b/test/spec/modules/adhashBidAdapter_spec.js @@ -178,105 +178,105 @@ describe('adhashBidAdapter', function () { }); it('should return empty array when there are bad words (full)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example badword text' + ' word'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (full cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text дума дума example дума text' + ' текст'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (partial)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadwordb badwordb example badwordbtext' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (partial, compound phrase)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbad wordb bad wordb example bad wordbtext' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (starts)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text startsWith starts text startsAgain' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (starts cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text стартТекст старт text стартТекст' + ' дума'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (ends)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text wordEnds ends text anotherends' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (ends cyrillic)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text ДругКрай край text ощеединкрай' + ' дума'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (combo)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'queen of england dies, the queen dies' + ' word'.repeat(993); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return empty array when there are bad words (regexp)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text xxxayyy zzxxxAyyyzz text xxxbyyy' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(0); }); it('should return non-empty array when there are not enough bad words (full)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example text' + ' word'.repeat(994); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are not enough bad words (partial)', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadwordb example' + ' word'.repeat(996); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are no-bad word matches', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text partialbadword example text' + ' word'.repeat(995); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there are bad words and good words', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return 'example text badword badword example badword goodWord goodWord ' + ' word'.repeat(992); }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); }); it('should return non-empty array when there is a problem with the brand-safety', function () { - bodyStub = sinon.stub(window.top.document.body, 'innerText').get(function() { + bodyStub = sinon.stub(window.top.document.body, 'textContent').get(function() { return null; }); expect(spec.interpretResponse(serverResponse, request).length).to.equal(1); diff --git a/test/spec/modules/adlooxAnalyticsAdapter_spec.js b/test/spec/modules/adlooxAnalyticsAdapter_spec.js index 450dd83f86d..fa8204a9dc5 100644 --- a/test/spec/modules/adlooxAnalyticsAdapter_spec.js +++ b/test/spec/modules/adlooxAnalyticsAdapter_spec.js @@ -169,7 +169,7 @@ describe('Adloox Analytics Adapter', function () { events.emit(EVENTS.BID_WON, bid); - const [urlInserted, moduleCode] = loadExternalScriptStub.getCall(0).args; + const [urlInserted, _, moduleCode] = loadExternalScriptStub.getCall(0).args; expect(urlInserted.substr(0, url.length)).to.equal(url); expect(moduleCode).to.equal(analyticsAdapterName); diff --git a/test/spec/modules/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index a9413860072..ae3b935619e 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,343 +1,467 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/admanBidAdapter.js'; -import {deepClone} from '../../../src/utils' +import { expect } from 'chai'; +import { spec } from '../../../modules/admanBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('AdmanAdapter', function () { - let bidBanner = { - bidId: '2dd581a2b6281d', - bidder: 'adman', - bidderRequestId: '145e1d6a7837c9', - params: { - placementId: 0 - }, - placementCode: 'placementid_0', - auctionId: '74f78609-a92d-4cf1-869f-1b244bbfb5d2', - mediaTypes: { - banner: { - sizes: [[300, 250]] +const bidder = 'adman'; + +describe('AdmanBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids }, - 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' + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 } - ] + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids } - }; + ]; - let bidVideo = deepClone({ - ...bidBanner, - params: { - placementId: 0, - traffic: 'video' - }, + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - video: { - playerSize: [300, 250] + [BANNER]: { + sizes: [[300, 250]] } + }, + params: { + } - }); + } - let bidderRequest = { - bidderCode: 'adman', - auctionId: 'fffffff-ffff-ffff-ffff-ffffffffffff', - bidderRequestId: 'ffffffffffffff', - start: 1472239426002, - auctionStart: 1472239426000, - timeout: 5000, - uspConsent: '1YN-', - gdprConsent: 'gdprConsent', + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'http://www.example.com', - reachedTop: true, + referer: 'https://test.com', + page: 'https://test.com' }, - bids: [bidBanner, bidVideo] - } + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; describe('isBidRequestValid', function () { - it('Should return true when placementId can be cast to a number', function () { - expect(spec.isBidRequestValid(bidBanner)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); - it('Should return false when placementId is not a number', function () { - bidBanner.params.placementId = 'aaa'; - expect(spec.isBidRequestValid(bidBanner)).to.be.false; - bidBanner.params.placementId = 0; + 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([bidBanner], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://pub.admanmedia.com/?c=o&m=multi'); }); - it('Should contain ccpa', function() { - expect(serverRequest.data.ccpa).to.be.an('string') - }) - it('Returns valid BANNER data if array of bids is valid', function () { - serverRequest = spec.buildRequests([bidBanner], bidderRequest); + 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', 'ccpa', 'gdpr'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + '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'); - 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', 'ext'); - expect(placement.schain).to.be.an('object') - expect(placement.ext).to.be.an('object') - expect(placement.ext).to.have.key('tid') - expect(placement.ext.tid).to.equal(bidBanner.transactionId); - expect(placement.placementId).to.be.a('number'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + 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.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); 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'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + 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 valid VIDEO data if array of bids is valid', function () { - serverRequest = spec.buildRequests([bidVideo], bidderRequest); + it('Returns data with gdprConsent and without uspConsent', function () { + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); - 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', 'eids', 'bidId', 'traffic', 'schain', 'bidFloor', - 'playerSize', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', - 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'ext'); - expect(placement.ext).to.be.an('object') - expect(placement.ext).to.have.key('tid') - expect(placement.ext.tid).to.equal(bidBanner.transactionId); - 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.bidFloor).to.be.an('number'); - } + expect(data.gdpr).to.exist; + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + expect(data.ccpa).to.not.exist; + delete bidderRequest.gdprConsent; }); - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + 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.placements).to.be.an('array').that.is.empty; + 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; }); }); - describe('buildRequests with user ids', function () { - bidBanner.userId = {} - bidBanner.userId.uid2 = { id: 'uid2id123' }; - let serverRequest = spec.buildRequests([bidBanner], bidderRequest); - it('Returns valid data if array of bids is valid', function () { + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); 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') - } - } - }); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { - it('(BANNER) Returns an array of valid server responses if response object is valid', function () { - const resBannerObject = { - body: [ { - requestId: '123', + it('Should interpret banner response', function () { + const banner = { + body: [{ mediaType: 'banner', - cpm: 0.3, - width: 320, - height: 50, - ad: '

Hello ad

', - ttl: 1000, - creativeId: '123asd', + width: 300, + height: 250, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], + dealId: '1', meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; - - const serverResponses = spec.interpretResponse(resBannerObject); - - 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', 'adomain'); - 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'); - } + 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('(VIDEO) Returns an array of valid server responses if response object is valid', function () { - const resVideoObject = { - body: [ { - requestId: '123', + it('Should interpret video response', function () { + const video = { + body: [{ + vastUrl: 'test.com', mediaType: 'video', - cpm: 0.3, - width: 320, - height: 50, - vastUrl: 'https://', - ttl: 1000, - creativeId: '123asd', + cpm: 0.5, + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], + dealId: '1', meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; + let videoResponses = spec.interpretResponse(video); + expect(videoResponses).to.be.an('array').that.is.not.empty; - const serverResponses = spec.interpretResponse(resVideoObject); - - 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', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); - 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.vastUrl).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'); - } + 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('(NATIVE) Returns an array of valid server responses if response object is valid', function () { - const resNativeObject = { - body: [ { - requestId: '123', + it('Should interpret native response', function () { + const native = { + body: [{ mediaType: 'native', - cpm: 0.3, - width: 320, - height: 50, native: { - title: 'title', - image: 'image', - impressionTrackers: [ 'https://' ] + clickUrl: 'test.com', + title: 'Test', + image: 'test.com', + impressionTrackers: ['test.com'], }, - ttl: 1000, - creativeId: '123asd', + ttl: 120, + cpm: 0.4, + requestId: '23fhj33i987f', + creativeId: '2', netRevenue: true, currency: 'USD', - adomain: ['example.com'], meta: { advertiserDomains: ['google.com'], advertiserId: 1234 } - } ] + }] }; + let nativeResponses = spec.interpretResponse(native); + expect(nativeResponses).to.be.an('array').that.is.not.empty; - const serverResponses = spec.interpretResponse(resNativeObject); - - 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', 'native', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'mediaType', 'meta', 'adomain'); - 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.native).to.be.an('object'); - 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'); - } + 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('Invalid mediaType in response', function () { - const resBadObject = { - body: [ { - mediaType: 'other', - requestId: '123', - cpm: 0.3, - ttl: 1000, - creativeId: '123asd', - currency: 'USD' - } ] + it('Should return an empty array if invalid banner response is passed', function () { + const invBanner = { + body: [{ + width: 300, + cpm: 0.4, + ad: 'Test', + requestId: '23fhj33i987f', + ttl: 120, + creativeId: '2', + netRevenue: true, + currency: 'USD', + dealId: '1' + }] }; - const serverResponses = spec.interpretResponse(resBadObject); - + 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 () { - const gdprConsent = { consentString: 'consentString', gdprApplies: 1 }; - const consentString = { consentString: 'consentString' } - let userSync = spec.getUserSyncs({}, {}, gdprConsent, consentString); - 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://sync.admanmedia.com/image?pbjs=1&gdpr=0&gdpr_consent=consentString&ccpa_consent=consentString&coppa=0'); + 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://sync.admanmedia.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://sync.admanmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://sync.admanmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/admaruBidAdapter_spec.js b/test/spec/modules/admaruBidAdapter_spec.js index 813a4ed8b29..05ec9eca67f 100644 --- a/test/spec/modules/admaruBidAdapter_spec.js +++ b/test/spec/modules/admaruBidAdapter_spec.js @@ -39,12 +39,12 @@ describe('Admaru Adapter', function () { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing pub_id or adspace_id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/admaticBidAdapter_spec.js b/test/spec/modules/admaticBidAdapter_spec.js index 8730f2c1a0d..50700a7b4e1 100644 --- a/test/spec/modules/admaticBidAdapter_spec.js +++ b/test/spec/modules/admaticBidAdapter_spec.js @@ -802,6 +802,7 @@ describe('admaticBidAdapter', () => { 'price': 0.01, 'type': 'banner', 'bidder': 'admatic', + 'currency': 'TRY', 'mime_type': { 'name': 'backfill', 'force': false @@ -816,6 +817,7 @@ describe('admaticBidAdapter', () => { 'width': 300, 'height': 250, 'price': 0.01, + 'currency': 'TRY', 'type': 'video', 'mime_type': { 'name': 'backfill', @@ -832,6 +834,7 @@ describe('admaticBidAdapter', () => { 'width': 1, 'height': 1, 'price': 0.01, + 'currency': 'TRY', 'type': 'native', 'mime_type': { 'name': 'backfill', @@ -843,6 +846,7 @@ describe('admaticBidAdapter', () => { 'iurl': 'https://www.admatic.com.tr' } ], + 'cur': 'TRY', 'queryId': 'cdnbh24rlv0hhkpfpln0', 'status': true }}; @@ -853,9 +857,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 300, height: 250, - currency: 'TRY', mediaType: 'banner', netRevenue: true, + currency: 'TRY', ad: '
', creativeId: '374', meta: { @@ -873,9 +877,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 300, height: 250, - currency: 'TRY', mediaType: 'video', netRevenue: true, + currency: 'TRY', vastXml: '', creativeId: '3741', meta: { @@ -893,9 +897,9 @@ describe('admaticBidAdapter', () => { cpm: 0.01, width: 1, height: 1, - currency: 'TRY', mediaType: 'native', netRevenue: true, + currency: 'TRY', native: { 'clickUrl': 'https://www.admatic.com.tr', 'impressionTrackers': ['https://www.admatic.com.tr'], @@ -1144,7 +1148,8 @@ describe('admaticBidAdapter', () => { let bids = { body: { data: [], 'queryId': 'cdnbh24rlv0hhkpfpln0', - 'status': true + 'status': true, + 'cur': 'TRY' }}; let result = spec.interpretResponse(bids, {data: request}); diff --git a/test/spec/modules/admixerBidAdapter_spec.js b/test/spec/modules/admixerBidAdapter_spec.js index e254d2f2ff7..1da6a58bea3 100644 --- a/test/spec/modules/admixerBidAdapter_spec.js +++ b/test/spec/modules/admixerBidAdapter_spec.js @@ -4,7 +4,7 @@ import {newBidder} from 'src/adapters/bidderFactory.js'; import {config} from '../../../src/config.js'; const BIDDER_CODE = 'admixer'; -const WL_BIDDER_CODE = 'admixerwl' +const RTB_BIDDER_CODE = 'rtbstack' 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'; @@ -37,11 +37,10 @@ describe('AdmixerAdapter', function () { auctionId: '1d1a030790a475', }; - let wlBid = { - bidder: WL_BIDDER_CODE, + let rtbBid = { + bidder: RTB_BIDDER_CODE, params: { - clientId: CLIENT_ID, - endpointId: ENDPOINT_ID, + tagId: ENDPOINT_ID, }, adUnitCode: 'adunit-code', sizes: [ @@ -56,25 +55,25 @@ describe('AdmixerAdapter', function () { it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return true when params required by WL found', function () { - expect(spec.isBidRequestValid(wlBid)).to.equal(true); + it('should return true when params required by RTB found', function () { + expect(spec.isBidRequestValid(rtbBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { placementId: 0, }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); - it('should return false when params required by WL are not passed', function () { - let wlBid = Object.assign({}, wlBid); - delete wlBid.params; - wlBid.params = { + it('should return false when params required by RTB are not passed', function () { + let invalidBid = Object.assign({}, rtbBid); + delete invalidBid.params; + invalidBid.params = { clientId: 0, }; - expect(spec.isBidRequestValid(wlBid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -118,7 +117,7 @@ describe('AdmixerAdapter', function () { 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 } }, + config: { bidderURL: ENDPOINT_URL_CUSTOM }, }); const request = config.runWithBidder(BIDDER_CODE, () => spec.buildRequests(validRequest, bidderRequest) @@ -133,9 +132,8 @@ describe('AdmixerAdapter', function () { validRequest: [ { bidder: bidder, - params: bidder === 'admixerwl' ? { - clientId: CLIENT_ID, - endpointId: ENDPOINT_ID + params: bidder === 'rtbstack' ? { + tagId: ENDPOINT_ID } : { zone: ZONE_ID, }, @@ -193,10 +191,16 @@ describe('AdmixerAdapter', function () { expect(request.url).to.equal('https://inv-nets.admixer.net/adxprebid.1.2.aspx'); expect(request.method).to.equal('POST'); }); - it('build request for admixerwl', function () { - const requestParams = requestParamsFor('admixerwl'); - const request = spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest); - expect(request.url).to.equal(`https://inv-nets-adxwl.admixer.com/adxwlprebid.aspx?client=${CLIENT_ID}`); + it('build request for rtbstack', function () { + const requestParams = requestParamsFor('rtbstack'); + config.setBidderConfig({ + bidders: ['rtbstack'], + config: { bidderURL: ENDPOINT_URL_CUSTOM }, + }); + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(requestParams.validRequest, requestParams.bidderRequest) + ); + expect(request.url).to.equal(ENDPOINT_URL_CUSTOM); expect(request.method).to.equal('POST'); }); }); @@ -222,12 +226,12 @@ describe('AdmixerAdapter', function () { }, }; it('gets floor', function () { - bidderRequest.getFloor = () => { + validRequest[0].getFloor = () => { return { floor: 0.6 }; }; const request = spec.buildRequests(validRequest, bidderRequest); const payload = request.data; - expect(payload.bidFloor).to.deep.equal(0.6); + expect(payload.imps[0].bidFloor).to.deep.equal(0.6); }); }); diff --git a/test/spec/modules/adnuntiusBidAdapter_spec.js b/test/spec/modules/adnuntiusBidAdapter_spec.js index 8bbe6c83deb..3c5bc713100 100644 --- a/test/spec/modules/adnuntiusBidAdapter_spec.js +++ b/test/spec/modules/adnuntiusBidAdapter_spec.js @@ -6,13 +6,14 @@ import { config } from 'src/config.js'; import * as utils from 'src/utils.js'; import { getStorageManager } from 'src/storageManager.js'; import { getGlobal } from '../../../src/prebidGlobal'; +import {getUnixTimestampFromNow} from 'src/utils.js'; describe('adnuntiusBidAdapter', function () { const URL = 'https://ads.adnuntius.delivery/i?tzo='; const EURO_URL = 'https://europe.delivery.adnuntius.com/i?tzo='; const usi = utils.generateUUID() - const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp() }, { exp: misc.getUnixTimestamp(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: misc.getUnixTimestamp(1) }, { key: 'valid', value: 'also-valid', exp: misc.getUnixTimestamp(1) }, { key: 'expired', value: 'fwefew', exp: misc.getUnixTimestamp() }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: misc.getUnixTimestamp(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: misc.getUnixTimestamp() }] + const meta = [{ key: 'valueless' }, { value: 'keyless' }, { key: 'voidAuIds' }, { key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow() }, { exp: getUnixTimestampFromNow(1) }] }, { key: 'valid-withnetwork', value: 'also-valid-network', network: 'the-network', exp: getUnixTimestampFromNow(1) }, { key: 'valid', value: 'also-valid', exp: getUnixTimestampFromNow(1) }, { key: 'expired', value: 'fwefew', exp: getUnixTimestampFromNow() }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow(), network: 'adnuntius' }, { key: 'usi', value: usi, exp: getUnixTimestampFromNow(100), network: 'adnuntius' }, { key: 'usi', value: 'should be skipped because timestamp', exp: getUnixTimestampFromNow() }] let storage; // need this to make the restore work correctly -- something to do with stubbing static prototype methods @@ -49,7 +50,6 @@ describe('adnuntiusBidAdapter', function () { const tzo = new Date().getTimezoneOffset(); const ENDPOINT_URL_BASE = `${URL}${tzo}&format=prebid`; const ENDPOINT_URL = `${ENDPOINT_URL_BASE}&userId=${usi}`; - const ENDPOINT_URL_VIDEO = `${ENDPOINT_URL_BASE}&userId=${usi}&tt=vast4`; const ENDPOINT_URL_NOCOOKIE = `${ENDPOINT_URL_BASE}&userId=${usi}&noCookies=true`; const ENDPOINT_URL_SEGMENTS = `${ENDPOINT_URL_BASE}&segments=segment1,segment2,segment3&userId=${usi}`; const ENDPOINT_URL_CONSENT = `${EURO_URL}${tzo}&format=prebid&consentString=consentString&gdpr=1&userId=${usi}`; @@ -101,7 +101,66 @@ describe('adnuntiusBidAdapter', function () { } }, } - ] + ]; + + const multiBidderInResponse = { + bid: [{ + bidder: 'adnuntius', + bidId: '3a602680158a85', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + } + } + }, + { + bidder: 'adnuntius', + params: { + auId: '381535', + network: '1287', + bidType: 'netBid', + targetId: 'fred', + }, + mediaTypes: { + banner: { + sizes: [[200, 200]] + }, + video: { + playerSize: [200, 200], + context: 'instream' + } + } + }] + }; + + const multiBidderRequest = [ + { + bidId: 'adn-0000000000000551', + bidder: 'adnuntius', + params: { + auId: '0000000000000551', + network: 'adnuntius', + }, + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream' + }, + banner: { + sizes: [[1640, 1480], [1600, 1400]], + } + }, + } + ]; const singleBidRequest = { bid: [ @@ -183,6 +242,131 @@ describe('adnuntiusBidAdapter', function () { } ]; + const multiFormatServerResponse = { + body: { + 'adUnits': [ + { + 'auId': '0000000000381535', + 'targetId': '3a602680158a85-video', + 'vastXml': '\n', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-453419729', + 'ads': [ + { + 'cpm': { + 'amount': 1500.0, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.5, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'adId': 'adn-id-615465411', + 'vastXml': '' + } + ] + }, + { + 'auId': '0000000000381535', + 'targetId': 'fred-video', + 'vastXml': '', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp--1809523040', + 'ads': [ + { + 'cpm': { + 'amount': 1500.0, + 'currency': 'NOK' + }, + 'bid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.5, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.5, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'adId': 'adn-id-344789675', + 'selectedColumn': '0', + 'selectedColumnPosition': '0', + 'vastXml': '\n', + } + ] + }, + { + 'auId': '0000000000381535', + 'targetId': '3a602680158a85', + 'html': '\u003C!DOCTYPE html\u003E\n\n\u003C/html\u003E', + 'matchedAdCount': 0, + 'responseId': '', + 'ads': [] + }, + { + 'auId': '0000000000381535', + 'renderOption': 'DIV', + 'targetId': 'fred', + 'html': '\u003C!DOCTYPE html\u003E\n\u003C\u003E\n\u003C/html\u003E', + 'matchedAdCount': 1, + 'responseId': 'adn-rsp-1620340740', + 'ads': [ + { + 'destinationUrlEsc': '', + 'cpm': { + 'amount': 1250.0, + 'currency': 'NOK' + }, + creativeWidth: 200, + creativeHeight: 240, + 'bid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'grossBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'netBid': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'cost': { + 'amount': 1.75, + 'currency': 'NOK' + }, + 'html': '\u003Ca \'\u003E\u003C/script\u003E', + } + ] + } + ], + 'network': '1287', + 'keywords': [] + } + }; + const serverResponse = { body: { 'adUnits': [ @@ -474,7 +658,12 @@ describe('adnuntiusBidAdapter', function () { return 'overridden-value'; }); - const request = spec.buildRequests(bidderRequests, {}); + const request = spec.buildRequests(bidderRequests, { + refererInfo: { + canonicalUrl: 'https://canonical.com/page.html', + page: 'https://canonical.com/something-else.html' + } + }); expect(request.length).to.equal(1); expect(request[0]).to.have.property('bid'); const bid = request[0].bid[0] @@ -482,7 +671,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0]).to.have.property('url'); expect(request[0].url).to.equal(ENDPOINT_URL.replace('format=prebid', 'format=prebid&so=overridden-value')); expect(request[0]).to.have.property('data'); - expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}]}'); + expect(request[0].data).to.equal('{"adUnits":[{"auId":"000000000008b6bc","targetId":"123","maxDeals":1,"dimensions":[[640,480],[600,400]]},{"auId":"0000000000000551","targetId":"adn-0000000000000551","dimensions":[[1640,1480],[1600,1400]]}],"context":"https://canonical.com/something-else.html","canonical":"https://canonical.com/page.html"}'); }); it('Test requests with no local storage', function () { @@ -505,7 +694,7 @@ describe('adnuntiusBidAdapter', function () { }); it('Test request changes for voided au ids', function () { - storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: misc.getUnixTimestamp(1) }, { auId: '0000000000000023', exp: misc.getUnixTimestamp(1) }] }])); + storage.setDataInLocalStorage('adn.metaData', JSON.stringify([{ key: 'voidAuIds', value: [{ auId: '11118b6bc', exp: getUnixTimestampFromNow(1) }, { auId: '0000000000000023', exp: getUnixTimestampFromNow(1) }] }])); const bRequests = bidderRequests.concat([{ bidId: 'adn-11118b6bc', bidder: 'adnuntius', @@ -559,14 +748,37 @@ describe('adnuntiusBidAdapter', function () { it('Test Video requests', function () { const request = spec.buildRequests(videoBidderRequest, {}); expect(request.length).to.equal(1); + + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(1); + expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551'); + expect(data.adUnits[0].adType).to.equal('VAST'); + expect(request[0]).to.have.property('bid'); const bid = request[0].bid[0] expect(bid).to.have.property('bidId'); expect(request[0]).to.have.property('url'); - expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); + expect(request[0].url).to.equal(ENDPOINT_URL); }); - it('should pass segments if available in config', function () { + it('Test multiformat requests', function () { + const request = spec.buildRequests(multiBidderRequest, {}); + expect(request.length).to.equal(1); + expect(request.data) + const data = JSON.parse(request[0].data); + expect(data.adUnits.length).to.equal(2); + expect(data.adUnits[0].targetId).to.equal('adn-0000000000000551'); + expect(data.adUnits[0]).not.to.have.property('adType'); + expect(data.adUnits[1].targetId).to.equal('adn-0000000000000551-video'); + expect(data.adUnits[1].adType).to.equal('VAST'); + expect(request[0]).to.have.property('bid'); + const bid = request[0].bid[0] + expect(bid).to.have.property('bidId'); + expect(request[0]).to.have.property('url'); + expect(request[0].url).to.equal(ENDPOINT_URL); + }); + + it('should pass segments if available in config and merge from targeting', function () { const ortb2 = { user: { data: [{ @@ -580,46 +792,142 @@ describe('adnuntiusBidAdapter', function () { } }; + bidderRequests[0].params.targeting = { + segments: ['merge-this', 'and-this'] + }; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS); + expect(request[0].url).to.equal(ENDPOINT_URL_SEGMENTS.replace('segment3', 'segment3,merge-this,and-this')); + + delete bidderRequests[0].params.targeting; }); + function countMatches(actualArray, expectedValue) { + return actualArray.filter(val => { + return JSON.stringify(val) === JSON.stringify(expectedValue); + }).length; + } + it('should pass site data ext as key values to ad server', function () { const ortb2 = { site: { ext: { data: { '12345': 'true', - '45678': 'true' + '45678': 'true', + '9090': 'should-be-retained' } } } }; - + bidderRequests[0].params.targeting = { + kv: { + 'merge': ['this'], + '9090': ['take it over'] + } + }; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); - expect(data.adUnits[0].kv).to.have.property('12345'); - expect(data.adUnits[0].kv['12345']).to.equal('true'); - expect(data.adUnits[0].kv).to.have.property('45678'); - expect(data.adUnits[0].kv['45678']).to.equal('true'); + expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(5); + + delete bidderRequests[0].params.targeting; }); - it('should skip passing site data ext if missing', function () { + it('should pass site data ext as key values to ad server with targeting in different format', function () { const ortb2 = { site: { ext: { + data: { + '12345': 'true', + '45678': 'true', + '9090': 'should-be-retained' + } } } }; + bidderRequests[0].params.targeting = { + kv: [ + {'merge': ['this']}, + {'9090': ['take it over']} + ] + }; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url') + const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, {'9090': ['take it over']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'merge': ['this']})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(5); + + delete bidderRequests[0].params.targeting; + }); + it('should pass site data ext as key values to ad server even if no kv targeting specified in params.targeting', function () { + const ortb2 = { + site: { + ext: { + data: { + '12345': 'true', + '45678': 'true', + '9090': 'should-be-retained' + } + } + } + }; const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') const data = JSON.parse(request[0].data); + expect(countMatches(data.adUnits[0].kv, {'9090': 'should-be-retained'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'45678': 'true'})).to.equal(1); + expect(countMatches(data.adUnits[0].kv, {'12345': 'true'})).to.equal(1); + expect(data.adUnits[0].kv.length).to.equal(3); + + delete bidderRequests[0].params.targeting; + }); + + it('should skip passing site ext if missing', function () { + const ortb2 = { + site: { + ext: { + } + } + }; + + delete bidderRequests[0].params.targeting; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url'); + const data = JSON.parse(request[0].data); + expect(data.adUnits[0]).to.not.have.property('kv'); + }); + + it('should skip passing site ext data if missing', function () { + const ortb2 = { + site: { + ext: { + data: {} + } + } + }; + + delete bidderRequests[0].params.targeting; + const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, { ortb2 })); + expect(request.length).to.equal(1); + expect(request[0]).to.have.property('url'); + const data = JSON.parse(request[0].data); expect(data.adUnits[0]).to.not.have.property('kv'); }); @@ -820,14 +1128,15 @@ describe('adnuntiusBidAdapter', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { - useCookie: false + useCookie: false, + advertiserTransparency: true } }); const request = config.runWithBidder('adnuntius', () => spec.buildRequests(bidderRequests, {})); expect(request.length).to.equal(1); expect(request[0]).to.have.property('url') - expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE); + expect(request[0].url).to.equal(ENDPOINT_URL_NOCOOKIE + '&advertiserTransparency=true'); }); }); @@ -898,7 +1207,7 @@ describe('adnuntiusBidAdapter', function () { expect(data.adUnits.length).to.equal(1); expect(data.adUnits[0].maxDeals).to.equal(5); }); - it('Should allow a minumum of 0 deals.', function () { + it('Should allow a minimum of 0 deals.', function () { config.setBidderConfig({ bidders: ['adnuntius'], }); @@ -925,7 +1234,9 @@ describe('adnuntiusBidAdapter', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { - maxDeals: 2 + maxDeals: 2, + useCookie: 'ignore-this', + advertiserTransparency: 'ignore-this-as-well' } }); @@ -1012,30 +1323,65 @@ describe('adnuntiusBidAdapter', function () { const usiEntry = results.find(entry => entry.key === 'usi' && entry.network === 'some-network-id'); expect(usiEntry.key).to.equal('usi'); expect(usiEntry.value).to.equal('from-api-server dude'); - expect(usiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + expect(usiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); expect(usiEntry.network).to.equal('some-network-id') const voidAuIdsEntry = results.find(entry => entry.key === 'voidAuIds'); expect(voidAuIdsEntry.key).to.equal('voidAuIds'); expect(voidAuIdsEntry.exp).to.equal(undefined); expect(voidAuIdsEntry.value[0].auId).to.equal('00000000000abcde'); - expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(voidAuIdsEntry.value[0].exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(voidAuIdsEntry.value[0].exp).to.be.lessThan(getUnixTimestampFromNow(2)); expect(voidAuIdsEntry.value[1].auId).to.equal('00000000000fffff'); - expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(voidAuIdsEntry.value[1].exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(voidAuIdsEntry.value[1].exp).to.be.lessThan(getUnixTimestampFromNow(2)); const validEntry = results.find(entry => entry.key === 'valid'); expect(validEntry.key).to.equal('valid'); expect(validEntry.value).to.equal('also-valid'); - expect(validEntry.exp).to.be.greaterThan(misc.getUnixTimestamp()); - expect(validEntry.exp).to.be.lessThan(misc.getUnixTimestamp(2)); + expect(validEntry.exp).to.be.greaterThan(getUnixTimestampFromNow()); + expect(validEntry.exp).to.be.lessThan(getUnixTimestampFromNow(2)); const randomApiEntry = results.find(entry => entry.key === 'randomApiKey'); expect(randomApiEntry.key).to.equal('randomApiKey'); expect(randomApiEntry.value).to.equal('randomApiValue'); expect(randomApiEntry.network).to.equal('some-network-id'); - expect(randomApiEntry.exp).to.be.greaterThan(misc.getUnixTimestamp(90)); + expect(randomApiEntry.exp).to.be.greaterThan(getUnixTimestampFromNow(90)); + }); + + it('should return valid response when passed valid multiformat server response', function () { + config.setBidderConfig({ + bidders: ['adnuntius'], + config: { + bidType: 'netBid', + maxDeals: 0 + } + }); + + const interpretedResponse = config.runWithBidder('adnuntius', () => spec.interpretResponse(multiFormatServerResponse, multiBidderInResponse)); + expect(interpretedResponse).to.have.lengthOf(2); + + let ad = multiFormatServerResponse.body.adUnits[0].ads[0]; + expect(interpretedResponse[0].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[0].cpm).to.equal(ad.netBid.amount * 1000); + 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.bid.currency); + expect(interpretedResponse[0].netRevenue).to.equal(false); + expect(interpretedResponse[0].ad).to.equal(multiFormatServerResponse.body.adUnits[0].html); + expect(interpretedResponse[0].ttl).to.equal(360); + + ad = multiFormatServerResponse.body.adUnits[3].ads[0]; + expect(interpretedResponse[1].bidderCode).to.equal('adnuntius'); + expect(interpretedResponse[1].cpm).to.equal(ad.netBid.amount * 1000); + expect(interpretedResponse[1].width).to.equal(Number(ad.creativeWidth)); + expect(interpretedResponse[1].height).to.equal(Number(ad.creativeHeight)); + expect(interpretedResponse[1].creativeId).to.equal(ad.creativeId); + expect(interpretedResponse[1].currency).to.equal(ad.bid.currency); + expect(interpretedResponse[1].netRevenue).to.equal(false); + expect(interpretedResponse[1].ad).to.equal(multiFormatServerResponse.body.adUnits[3].html); + expect(interpretedResponse[1].ttl).to.equal(360); }); it('should not process valid response when passed alt bidder that is an adndeal', function () { diff --git a/test/spec/modules/adoceanBidAdapter_spec.js b/test/spec/modules/adoceanBidAdapter_spec.js index 080b5bd5d1d..cff5e77d95b 100644 --- a/test/spec/modules/adoceanBidAdapter_spec.js +++ b/test/spec/modules/adoceanBidAdapter_spec.js @@ -36,13 +36,13 @@ describe('AdoceanAdapter', function () { }); it('should return false when required params are not passed', function () { - const bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'masterId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/adomikAnalyticsAdapter_spec.js b/test/spec/modules/adomikAnalyticsAdapter_spec.js deleted file mode 100644 index 703e6ed8992..00000000000 --- a/test/spec/modules/adomikAnalyticsAdapter_spec.js +++ /dev/null @@ -1,253 +0,0 @@ -import adomikAnalytics from 'modules/adomikAnalyticsAdapter.js'; -import { expect } from 'chai'; -import {EVENTS} from 'src/constants.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('Adomik Prebid Analytic', function () { - let sendEventStub; - let sendWonEventStub; - let clock; - - beforeEach(function () { - clock = sinon.useFakeTimers(); - sinon.spy(adomikAnalytics, 'track'); - sendEventStub = sinon.stub(adomikAnalytics, 'sendTypedEvent'); - sendWonEventStub = sinon.stub(adomikAnalytics, 'sendWonEvent'); - sinon.stub(events, 'getEvents').returns([]); - adomikAnalytics.currentContext = undefined; - - adapterManager.registerAnalyticsAdapter({ - code: 'adomik', - adapter: adomikAnalytics - }); - }); - - afterEach(function () { - adomikAnalytics.disableAnalytics(); - clock.restore(); - adomikAnalytics.track.restore(); - sendEventStub.restore(); - sendWonEventStub.restore(); - events.getEvents.restore(); - }); - - describe('adomikAnalytics.enableAnalytics', function () { - it('should catch all events', function (done) { - const initOptions = { - id: '123456', - url: 'testurl' - }; - - const bid = { - bidderCode: 'adomik_test_bid', - width: 10, - height: 10, - statusMessage: 'Bid available', - adId: '1234', - auctionId: '', - responseTimestamp: 1496410856397, - requestTimestamp: 1496410856295, - cpm: 0.1, - bidder: 'biddertest', - adUnitCode: '0000', - timeToRespond: 100, - placementCode: 'placementtest' - } - - // Step 1: Initialize adapter - adapterManager.enableAnalytics({ - provider: 'adomik', - options: initOptions - }); - expect(adomikAnalytics.currentContext).to.deep.equal({ - uid: '123456', - url: 'testurl', - sampling: undefined, - id: '', - timeouted: false - }); - - // Step 2: Send init auction event - events.emit(EVENTS.AUCTION_INIT, {config: initOptions, auctionId: 'test-test-test'}); - - expect(adomikAnalytics.currentContext).to.deep.equal({ - uid: '123456', - url: 'testurl', - sampling: undefined, - id: 'test-test-test', - timeouted: false - }); - - // Step 3: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, { bids: [bid] }); - - expect(adomikAnalytics.bucketEvents.length).to.equal(1); - expect(adomikAnalytics.bucketEvents[0]).to.deep.equal({ - type: 'request', - event: { - bidder: 'BIDDERTEST', - placementCode: '0000', - } - }); - - // Step 4: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bid); - - expect(adomikAnalytics.bucketEvents.length).to.equal(2); - expect(adomikAnalytics.bucketEvents[1]).to.deep.equal({ - type: 'response', - event: { - bidder: 'ADOMIK_TEST_BID', - placementCode: '0000', - id: '1234', - status: 'VALID', - cpm: 0.1, - size: { - width: 10, - height: 10 - }, - timeToRespond: 100, - afterTimeout: false, - } - }); - - // Step 5: Send bid won event - events.emit(EVENTS.BID_WON, bid); - - expect(adomikAnalytics.bucketEvents.length).to.equal(2); - - // Step 6: Send bid timeout event - events.emit(EVENTS.BID_TIMEOUT, {}); - - expect(adomikAnalytics.currentContext.timeouted).to.equal(true); - - // Step 7: Send auction end event - events.emit(EVENTS.AUCTION_END, {}); - - setTimeout(function() { - sinon.assert.callCount(sendEventStub, 1); - sinon.assert.callCount(sendWonEventStub, 1); - done(); - }, 3000); - - clock.tick(5000); - - sinon.assert.callCount(adomikAnalytics.track, 6); - }); - - describe('when sampling is undefined', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl' } - }); - }); - - it('is enabled', function () { - expect(adomikAnalytics.currentContext).is.not.null; - }); - }); - - describe('when sampling is 0', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl', sampling: 0 } - }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when sampling is 1', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ - provider: 'adomik', - options: { id: '123456', url: 'testurl', sampling: 1 } - }); - }); - - it('is enabled', function () { - expect(adomikAnalytics.currentContext).is.not.null; - }); - }); - - describe('when options is not defined', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when id is not defined in options', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik', url: 'xxx' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - - describe('when url is not defined in options', function () { - beforeEach(function() { - adapterManager.enableAnalytics({ provider: 'adomik', id: 'xxx' }); - }); - - it('is disabled', function () { - expect(adomikAnalytics.currentContext).to.equal(undefined); - }); - }); - }); - - describe('adomikAnalytics.getKeyValues', function () { - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - - describe('when test is in scope', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_AdomikTestInScope', true); - }); - - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - - describe('when key values are defined', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_AdomikTest', '{"testId":"12345","testOptionLabel":"1000"}'); - }); - - it('returns key values', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal('12345'); - expect(testValue).to.equal('1000'); - }); - - describe('when preventTest is on', function () { - beforeEach(function () { - sessionStorage.setItem(window.location.hostname + '_NoAdomikTest', true); - }); - - it('returns [undefined, undefined]', function () { - let [testId, testValue] = adomikAnalytics.getKeyValues() - expect(testId).to.equal(undefined); - expect(testValue).to.equal(undefined); - }); - }); - }); - }); - }); -}); diff --git a/test/spec/modules/adotBidAdapter_spec.js b/test/spec/modules/adotBidAdapter_spec.js index 34252e00f9e..df628088bb0 100644 --- a/test/spec/modules/adotBidAdapter_spec.js +++ b/test/spec/modules/adotBidAdapter_spec.js @@ -124,7 +124,7 @@ describe('Adot Adapter', function () { it('should build request (video)', function () { const bidderRequestId = 'bidderRequestId'; - const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], placement: 'placement', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; + const validBidRequests = [{ bidderRequestId, mediaTypes: {} }, { bidderRequestId, bidId: 'bidId', mediaTypes: { video: { playerSize: [[300, 250]], minduration: 1, maxduration: 2, api: 'api', linearity: 'linearity', mimes: [], plcmt: '1', playbackmethod: 'playbackmethod', protocols: 'protocol', startdelay: 'startdelay' } }, params: { placementId: 'placementId', adUnitCode: 200 } }]; const bidderRequest = { position: 2, refererInfo: { page: 'http://localhost.com', domain: 'localhost.com' }, gdprConsent: { consentString: 'consentString', gdprApplies: true }, userId: { pubProvidedId: 'userId' }, schain: { ver: '1.0' } }; const request = spec.buildRequests(validBidRequests, bidderRequest); @@ -144,7 +144,7 @@ describe('Adot Adapter', function () { maxduration: 2, mimes: [], minduration: 1, - placement: 'placement', + placement: '1', playbackmethod: 'playbackmethod', pos: 0, protocols: 'protocol', diff --git a/test/spec/modules/adpartnerBidAdapter_spec.js b/test/spec/modules/adpartnerBidAdapter_spec.js index d9f9b0d0074..597974acce8 100644 --- a/test/spec/modules/adpartnerBidAdapter_spec.js +++ b/test/spec/modules/adpartnerBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/adpartnerBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as miUtils from 'libraries/mediaImpactUtils/index.js'; const BIDDER_CODE = 'adpartner'; @@ -117,7 +118,7 @@ describe('AdpartnerAdapter', function () { describe('joinSizesToString', function () { it('success convert sizes list to string', function () { - const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]); + const sizesStr = miUtils.joinSizesToString([[300, 250], [300, 600]]); expect(sizesStr).to.equal('300x250|300x600'); }); }); @@ -238,7 +239,7 @@ describe('AdpartnerAdapter', function () { let ajaxStub; beforeEach(() => { - ajaxStub = sinon.stub(spec, 'postRequest') + ajaxStub = sinon.stub(miUtils, 'postRequest') }) afterEach(() => { diff --git a/test/spec/modules/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 5efed4ec5ab..4199145e80a 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -1,131 +1,257 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/adprimeBidAdapter.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/adprimeBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; -describe('AdprimebBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'adprime', +const bidder = 'adprime'; + +describe('AdprimeBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - banner: { - sizes: [[300, 250]], + [BANNER]: { + sizes: [[300, 250]] } }, params: { - placementId: 'testBanner' + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://delta.adprime.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'identeties', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'keywords', 'audiences', 'bidFloor'); - expect(placement.placementId).to.equal('testBanner'); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.adFormat).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.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 valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.keywords).to.be.an('array'); + expect(placement.audiences).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); 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); + 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; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - 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 () { + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); 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') - } - }); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -141,7 +267,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -149,15 +278,15 @@ describe('AdprimebBidAdapter', function () { 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('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.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { @@ -172,7 +301,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -206,7 +338,10 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -293,6 +428,7 @@ describe('AdprimebBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -317,5 +453,17 @@ describe('AdprimebBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://sync.adprime.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://sync.adprime.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/adqueryIdSystem_spec.js b/test/spec/modules/adqueryIdSystem_spec.js index 7952f23189e..9b7304d1984 100644 --- a/test/spec/modules/adqueryIdSystem_spec.js +++ b/test/spec/modules/adqueryIdSystem_spec.js @@ -1,6 +1,9 @@ import {adqueryIdSubmodule, storage} from 'modules/adqueryIdSystem.js'; import {server} from 'test/mocks/xhr.js'; import sinon from 'sinon'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const config = { storage: { @@ -58,4 +61,23 @@ describe('AdqueryIdSystem', function () { expect(callbackSpy.lastCall.lastArg).to.deep.equal('testqid'); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(adqueryIdSubmodule); + }); + it('qid', function() { + const userId = { + qid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adquery.io', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/adrelevantisBidAdapter_spec.js b/test/spec/modules/adrelevantisBidAdapter_spec.js index 7f24176e850..db637663f39 100644 --- a/test/spec/modules/adrelevantisBidAdapter_spec.js +++ b/test/spec/modules/adrelevantisBidAdapter_spec.js @@ -34,12 +34,12 @@ describe('AdrelevantisAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/ads_interactiveBidAdapter_spec.js b/test/spec/modules/ads_interactiveBidAdapter_spec.js new file mode 100644 index 00000000000..273df230287 --- /dev/null +++ b/test/spec/modules/ads_interactiveBidAdapter_spec.js @@ -0,0 +1,515 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/ads_interactiveBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'ads_interactive'; + +describe('AdsInteractiveBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + 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://bntb.adsinteractive.com/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.include.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://cstb.adsinteractive.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://cstb.adsinteractive.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cstb.adsinteractive.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/adtelligentBidAdapter_spec.js b/test/spec/modules/adtelligentBidAdapter_spec.js index 0acbaa06f5b..b03bba95357 100644 --- a/test/spec/modules/adtelligentBidAdapter_spec.js +++ b/test/spec/modules/adtelligentBidAdapter_spec.js @@ -16,7 +16,6 @@ const aliasEP = { 'janet': 'https://ghb.bidder.jmgads.com/v2/auction/', 'ocm': 'https://ghb.cenarius.orangeclickmedia.com/v2/auction/', '9dotsmedia': 'https://ghb.platform.audiodots.com/v2/auction/', - 'copper6': 'https://ghb.app.copper6.com/v2/auction/', 'indicue': 'https://ghb.console.indicue.com/v2/auction/', }; diff --git a/test/spec/modules/advRedAnalyticsAdapter_spec.js b/test/spec/modules/advRedAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c493710ab53 --- /dev/null +++ b/test/spec/modules/advRedAnalyticsAdapter_spec.js @@ -0,0 +1,114 @@ +import advRedAnalytics from 'modules/advRedAnalyticsAdapter.js'; +import {expect} from 'chai'; +import {server} from 'test/mocks/xhr.js'; +import {expectEvents} from '../../helpers/analytics.js'; +import { EVENTS } from 'src/constants.js'; +import sinon from 'sinon'; + +let events = require('src/events'); + +describe('AdvRed Analytics Adapter', function () { + let bidWonEvent = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'adId': '1ebb82ec35375e', + 'mediaType': 'banner', + 'cpm': 0.5, + 'requestId': '1582271863760569973', + 'creative_id': '96846035', + 'creativeId': '96846035', + 'ttl': 60, + 'currency': 'USD', + 'netRevenue': true, + 'auctionId': '9c7b70b9-b6ab-4439-9e71-b7b382797c18', + 'responseTimestamp': 1537521629657, + 'requestTimestamp': 1537521629331, + 'bidder': 'appnexus', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 326, + 'size': '300x250', + 'status': 'rendered', + 'eventType': 'bidWon', + 'ad': 'some ad', + 'adUrl': 'ad url' + }; + + describe('AdvRed Analytic tests', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + advRedAnalytics.disableAnalytics(); + events.getEvents.restore(); + }); + + it('support custom endpoint', function () { + let custom_endpoint = 'custom url'; + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + url: custom_endpoint, + publisherId: '1234567890' + } + }); + + expect(advRedAnalytics.getOptions().url).to.equal(custom_endpoint); + }); + + it('bid won event', function() { + let publisherId = '1234567890'; + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: publisherId + } + }); + + events.emit(EVENTS.BID_WON, bidWonEvent); + advRedAnalytics.sendEvents(); + + expect(server.requests.length).to.equal(1); + expect(server.requests[0].url).to.equal('https://api.adv.red/api/event'); + + const message = JSON.parse(server.requests[0].requestBody); + expect(message.pwId).to.exist; + expect(message.publisherId).to.equal(publisherId); + expect(message.events.length).to.equal(1); + expect(message.events[0].eventType).to.equal('bidWon'); + expect(message.events[0].ad).to.be.undefined; + expect(message.events[0].adUrl).to.be.undefined; + }); + + it('track event', function () { + sinon.spy(advRedAnalytics, 'track'); + + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '1234567890' + } + }); + + expectEvents().to.beTrackedBy(advRedAnalytics.track); + }); + }); + + describe('pageUrl detection', function () { + afterEach(function () { + advRedAnalytics.disableAnalytics() + }); + it('check pageUrl property', function () { + advRedAnalytics.enableAnalytics({ + provider: 'advRed', + options: { + publisherId: '1234567890' + } + }); + + const message = JSON.parse(server.requests[0].requestBody); + expect(message.pageUrl).to.equal(window.top.location.href); + }); + }); +}); diff --git a/test/spec/modules/adverxoBidAdapter_spec.js b/test/spec/modules/adverxoBidAdapter_spec.js new file mode 100644 index 00000000000..e3b98d49f07 --- /dev/null +++ b/test/spec/modules/adverxoBidAdapter_spec.js @@ -0,0 +1,725 @@ +import {expect} from 'chai'; +import {spec} from 'modules/adverxoBidAdapter.js'; +import {config} from 'src/config'; + +describe('Adverxo Bid Adapter', () => { + function makeBidRequestWithParams(params) { + return { + bidId: '2e9f38ea93bb9e', + bidder: 'adverxo', + adUnitCode: 'adunit-code', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: params, + bidderRequestId: 'test-bidder-request-id' + }; + } + + const bannerBidRequests = [ + { + bidId: 'bid-banner', + bidder: 'adverxo', + adUnitCode: 'adunit-code', + userIdAsEids: [{ + 'source': 'pubcid.org', + 'uids': [{ + 'atype': 1, + 'id': '01EAJWWNEPN3CYMM5N8M5VXY22' + }] + }], + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample', + }, + bidderRequestId: 'test-bidder-request-id', + }, + ]; + + const bannerBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: bannerBidRequests, + auctionId: 'new-auction-id' + }; + + const nativeOrtbRequest = { + assets: [ + { + id: 1, + required: 1, + img: {type: 3, w: 150, h: 50} + }, + { + id: 2, + required: 1, + title: {len: 80} + }, + { + id: 3, + required: 0, + data: {type: 1} + } + ] + }; + + const nativeBidRequests = [ + { + bidId: 'bid-native', + mediaTypes: { + native: { + ortb: nativeOrtbRequest + } + }, + nativeOrtbRequest, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + }, + ]; + + const nativeBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: nativeBidRequests, + auctionId: 'new-auction-id' + }; + + const videoInstreamBidRequests = [ + { + bidId: 'bid-video', + mediaTypes: { + video: { + context: 'instream', + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + } + ]; + + const videoInstreamBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: videoInstreamBidRequests, + auctionId: 'new-auction-id' + }; + + const videoOutstreamBidRequests = [ + { + bidId: 'bid-video', + mediaTypes: { + video: { + context: 'outstream', + playerSize: [400, 300], + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }, + params: { + host: 'bid.example.com', + adUnitId: 1, + auth: 'authExample' + } + } + ]; + + const videoOutstreamBidderRequest = { + bidderCode: 'adverxo', + bidderRequestId: 'test-bidder-request-id', + bids: videoOutstreamBidRequests, + auctionId: 'new-auction-id' + }; + + afterEach(function () { + config.resetConfig(); + }); + + describe('isBidRequestValid', function () { + it('should validate bid request with valid params', () => { + const validBid = makeBidRequestWithParams({ + adUnitId: 1, + auth: 'authExample', + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(validBid); + + expect(isValid).to.be.true; + }); + + it('should not validate bid request with empty params', () => { + const invalidBid = makeBidRequestWithParams({}); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should not validate bid request with missing param(adUnitId)', () => { + const invalidBid = makeBidRequestWithParams({ + auth: 'authExample', + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should not validate bid request with missing param(auth)', () => { + const invalidBid = makeBidRequestWithParams({ + adUnitId: 1, + host: 'www.bidExample.com' + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.false; + }); + + it('should validate bid request with missing param(host)', () => { + const invalidBid = makeBidRequestWithParams({ + adUnitId: 1, + auth: 'authExample', + }); + + const isValid = spec.isBidRequestValid(invalidBid); + + expect(isValid).to.be.true; + }); + }); + + describe('buildRequests', () => { + it('should add eids information to the request', function () { + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + + expect(request.data.user.ext.eids).to.exist; + expect(request.data.user.ext.eids).to.deep.equal(bannerBidRequests[0].userIdAsEids); + }); + + it('should use correct bidUrl for an alias', () => { + const bidRequests = [ + { + bidder: 'bidsmind', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + adUnitId: 1, + auth: 'authExample', + } + }, + ]; + + const bidderRequest = { + bidderCode: 'bidsmind', + bids: bidRequests, + }; + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://egrevirda.com/pickpbs?id=1&auth=authExample'); + }); + + it('should use correct default bidUrl', () => { + const bidRequests = [ + { + bidder: 'adverxo', + mediaTypes: {banner: {sizes: [[300, 250]]}}, + params: { + adUnitId: 1, + auth: 'authExample', + } + }, + ]; + + const bidderRequest = { + bidderCode: 'adverxo', + bids: bidRequests + }; + + const request = spec.buildRequests(bidRequests, bidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://js.pbsadverxo.com/pickpbs?id=1&auth=authExample'); + }); + + it('should build post request for banner', () => { + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + expect(request.data.device.ip).to.equal('caller'); + expect(request.data.ext.avx_add_vast_url).to.equal(1); + }); + + if (FEATURES.NATIVE) { + it('should build post request for native', () => { + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + + const nativeRequest = JSON.parse(request.data.imp[0]['native'].request); + + expect(nativeRequest.assets).to.have.lengthOf(3); + + expect(nativeRequest.assets[0]).to.deep.equal({ + id: 1, + required: 1, + img: {w: 150, h: 50, type: 3} + }); + + expect(nativeRequest.assets[1]).to.deep.equal({ + id: 2, + required: 1, + title: {len: 80} + }); + + expect(nativeRequest.assets[2]).to.deep.equal({ + id: 3, + required: 0, + data: {type: 1} + }); + }); + } + + if (FEATURES.VIDEO) { + it('should build post request for video', function () { + const request = spec.buildRequests(videoInstreamBidRequests, videoInstreamBidderRequest)[0]; + + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://bid.example.com/pickpbs?id=1&auth=authExample'); + + const ortbRequest = request.data; + + expect(ortbRequest.imp).to.have.lengthOf(1); + + expect(ortbRequest.imp[0]).to.deep.equal({ + id: 'bid-video', + secure: 1, + video: { + w: 400, + h: 300, + minduration: 5, + maxduration: 10, + startdelay: 0, + skip: 1, + minbitrate: 200, + protocols: [1, 2, 4] + } + }); + }); + } + + it('should add bid floor to request', function () { + const bannerBidRequestWithFloor = { + ...bannerBidRequests[0], + getFloor: () => ({currency: 'USD', floor: 3}) + }; + + const request = spec.buildRequests([bannerBidRequestWithFloor], {})[0].data; + + expect(request.imp[0].bidfloor).to.equal(3); + expect(request.imp[0].bidfloorcur).to.equal('USD'); + }); + }); + + describe('interpretResponse', () => { + it('should return empty array if serverResponse is not defined', () => { + const bidRequest = spec.buildRequests(bannerBidRequests, bannerBidderRequest); + const bids = spec.interpretResponse(undefined, bidRequest); + + expect(bids.length).to.equal(0); + }); + + it('should interpret banner response', () => { + const bidResponse = { + body: { + id: 'bid-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-banner', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 1, + adomain: ['test.com'], + adm: '
' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'banner', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + requestId: 'bid-banner', + ttl: 60, + width: 300, + ad: '
' + }, + ]; + + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + + it('should replace openrtb auction price macro', () => { + const bidResponse = { + body: { + id: 'bid-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-banner', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 1, + adomain: ['test.com'], + adm: '' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const request = spec.buildRequests(bannerBidRequests, bannerBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids[0].ad).to.equal(''); + }); + + if (FEATURES.NATIVE) { + it('should interpret native response', () => { + const bidResponse = { + body: { + id: 'native-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-native', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 4, + adomain: ['test.com'], + adm: '{"native":{"assets":[{"id":2,"title":{"text":"Title"}},{"id":3,"data":{"value":"Description"}},{"id":1,"img":{"url":"http://example.com?img","w":150,"h":50}}],"link":{"url":"http://example.com?link"}}}' + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'native', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + requestId: 'bid-native', + ttl: 60, + width: 300, + native: { + ortb: { + assets: [ + {id: 2, title: {text: 'Title'}}, + {id: 3, data: {value: 'Description'}}, + {id: 1, img: {url: 'http://example.com?img', w: 150, h: 50}} + ], + link: {url: 'http://example.com?link'} + } + } + } + ]; + + const request = spec.buildRequests(nativeBidRequests, nativeBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + } + + if (FEATURES.VIDEO) { + it('should interpret video instream response', () => { + const bidResponse = { + body: { + id: 'video-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-video', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 2, + adomain: ['test.com'], + adm: 'vastXml', + ext: { + avx_vast_url: 'vastUrl' + } + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'video', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + playerHeight: 300, + playerWidth: 400, + requestId: 'bid-video', + ttl: 60, + vastUrl: 'vastUrl', + vastXml: 'vastXml', + width: 300 + } + ]; + + const request = spec.buildRequests(videoInstreamBidRequests, videoInstreamBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids).to.deep.equal(expectedBids); + }); + + it('should interpret video outstream response', () => { + const bidResponse = { + body: { + id: 'video-response', + cur: 'USD', + seatbid: [ + { + bid: [ + { + impid: 'bid-video', + crid: 'creative-id', + cur: 'USD', + price: 2, + w: 300, + h: 250, + mtype: 2, + adomain: ['test.com'], + adm: 'vastXml', + ext: { + avx_vast_url: 'vastUrl', + avx_video_renderer_url: 'videoRendererUrl', + } + }, + ], + seat: 'test-seat', + }, + ], + }, + }; + + const expectedBids = [ + { + avxVideoRendererUrl: 'videoRendererUrl', + cpm: 2, + creativeId: 'creative-id', + creative_id: 'creative-id', + currency: 'USD', + height: 250, + mediaType: 'video', + meta: { + advertiserDomains: ['test.com'], + }, + netRevenue: true, + playerHeight: 300, + playerWidth: 400, + requestId: 'bid-video', + ttl: 60, + vastUrl: 'vastUrl', + vastXml: 'vastXml', + width: 300 + } + ]; + + const request = spec.buildRequests(videoOutstreamBidRequests, videoOutstreamBidderRequest)[0]; + const bids = spec.interpretResponse(bidResponse, request); + + expect(bids[0].renderer.url).to.equal('videoRendererUrl'); + + delete (bids[0].renderer); + expect(bids).to.deep.equal(expectedBids); + }); + } + }); + + describe('getUserSyncs', () => { + const exampleUrl = 'https://example.com/usync?id=5'; + const iframeConfig = {iframeEnabled: true}; + + const responses = [{ + body: {ext: {avx_usync: [exampleUrl]}} + }]; + + const responseWithoutQueryString = [{ + body: {ext: {avx_usync: ['https://example.com/usync/sf/5']}} + }]; + + it('should not return empty list if not allowed', function () { + expect(spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: false + }, responses, undefined, undefined, undefined)).to.be.empty; + }); + + it('should not return iframe if not allowed', function () { + expect(spec.getUserSyncs({ + iframeEnabled: false, + pixelEnabled: true + }, responses, undefined, undefined, undefined)).to.deep.equal([{ + type: 'image', url: `${exampleUrl}&type=image` + }]); + }); + + it('should add query string to url when missing', function () { + expect(spec.getUserSyncs(iframeConfig, responseWithoutQueryString, undefined, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `https://example.com/usync/sf/5?type=iframe` + }]); + }); + + it('should not add parameters if not provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, undefined, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should add GDPR parameters if provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, {gdprApplies: true}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=1&gdpr_consent=` + }]); + + expect(spec.getUserSyncs(iframeConfig, responses, + {gdprApplies: true, consentString: 'foo?'}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=1&gdpr_consent=foo%3F` + }]); + + expect(spec.getUserSyncs(iframeConfig, responses, + {gdprApplies: false, consentString: 'bar'}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gdpr=0&gdpr_consent=bar` + }]); + }); + + it('should add CCPA parameters if provided', function () { + expect(spec.getUserSyncs(iframeConfig, responses, undefined, 'foo?', undefined)).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&us_privacy=foo%3F` + }]); + }); + + it('should not apply if not gppConsent.gppString', function () { + const gppConsent = {gppString: '', applicableSections: [123]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should not apply if not gppConsent.applicableSections', function () { + const gppConsent = {gppString: '', applicableSections: undefined}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should not apply if empty gppConsent.applicableSections', function () { + const gppConsent = {gppString: '', applicableSections: []}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe` + }]); + }); + + it('should apply if all above are available', function () { + const gppConsent = {gppString: 'foo?', applicableSections: [123]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gpp=foo%3F&gpp_sid=123` + }]); + }); + + it('should support multiple sections', function () { + const gppConsent = {gppString: 'foo', applicableSections: [123, 456]}; + const result = spec.getUserSyncs(iframeConfig, responses, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', url: `${exampleUrl}&type=iframe&gpp=foo&gpp_sid=123%2C456` + }]); + }); + }); +}); diff --git a/test/spec/modules/adxcgBidAdapter_spec.js b/test/spec/modules/adxcgBidAdapter_spec.js index e07e3a6e5d4..25e35a58557 100644 --- a/test/spec/modules/adxcgBidAdapter_spec.js +++ b/test/spec/modules/adxcgBidAdapter_spec.js @@ -104,9 +104,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, bidId: 'bid12345', params: { - cp: 'p10000', - ct: 't10000', - cf: '300x250', adzoneid: '77' } }, { @@ -118,9 +115,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, bidId: 'bid23456', params: { - cp: 'p10000', - ct: 't20000', - cf: '728x90', adzoneid: '77' } }]; @@ -160,8 +154,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, nativeOrtbRequest, params: { - cp: 'p10000', - ct: 't10000', + adzoneid: '77' } }]; @@ -182,8 +175,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { } }, params: { - cp: 'p10000', - ct: 't10000', adzoneid: '77' } }]; @@ -196,9 +187,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, bidId: 'bid12345', params: { - cp: 'p10000', - ct: 't10000', - cf: '1x1', adzoneid: '77', extra_key1: 'extra_val1', extra_key2: 12345, @@ -219,9 +207,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, bidId: 'bid12345', params: { - cp: 'p10000', - ct: 't10000', - cf: '1x1', adzoneid: '77', bcat: ['IAB-1', 'IAB-20'], battr: [1, 2, 3], @@ -259,18 +244,15 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { // site object expect(ortbRequest.site).to.not.equal(null); expect(ortbRequest.site.publisher).to.not.equal(null); - // expect(ortbRequest.site.publisher.id).to.equal('p10000'); expect(ortbRequest.site.page).to.equal('https://publisher.com/home'); expect(ortbRequest.imp).to.have.lengthOf(2); // device object expect(ortbRequest.device).to.not.equal(null); expect(ortbRequest.device.ua).to.equal(navigator.userAgent); // slot 1 - // expect(ortbRequest.imp[0].tagid).to.equal('t10000'); expect(ortbRequest.imp[0].banner).to.not.equal(null); expect(ortbRequest.imp[0].banner.format).to.deep.eq([{ 'w': 728, 'h': 90 }, { 'w': 160, 'h': 600 }]); // slot 2 - // expect(ortbRequest.imp[1].tagid).to.equal('t20000'); expect(ortbRequest.imp[1].banner).to.not.equal(null); expect(ortbRequest.imp[1].banner.format).to.deep.eq([{ 'w': 728, 'h': 90 }]); }); @@ -477,7 +459,7 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, gdprConsent: { gdprApplies: true, - consentString: 'serialized_gpdr_data' + consentString: 'serialized_gdpr_data' }, ortb2: { user: { @@ -536,7 +518,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { page: 'http://pub.com/news', ref: 'http://google.com', publisher: { - // id: 'p10000', domain: 'pub.com' } }); @@ -552,8 +533,6 @@ describe('adxcg v8 oRtbConverter Adapter Tests', function () { }, bidId: 'bid12345', params: { - cp: 'p10000', - ct: 't10000', adzoneid: '77', extra_key1: 'extra_val1', extra_key2: 12345 diff --git a/test/spec/modules/adyoulikeBidAdapter_spec.js b/test/spec/modules/adyoulikeBidAdapter_spec.js index bafa031cd25..367fc62c719 100644 --- a/test/spec/modules/adyoulikeBidAdapter_spec.js +++ b/test/spec/modules/adyoulikeBidAdapter_spec.js @@ -619,19 +619,19 @@ describe('Adyoulike Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.size; + let invalidBid = Object.assign({}, bid); + delete invalidBid.sizes; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement': 0 }; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/aidemBidAdapter_spec.js b/test/spec/modules/aidemBidAdapter_spec.js index 3de348197b2..c07b999afe6 100644 --- a/test/spec/modules/aidemBidAdapter_spec.js +++ b/test/spec/modules/aidemBidAdapter_spec.js @@ -168,7 +168,7 @@ const DEFAULT_VALID_BANNER_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -193,7 +193,7 @@ const DEFAULT_VALID_VIDEO_REQUESTS = [ }, params: { siteId: '1', - placementId: '13144370' + placementId: '13144370', }, src: 'client', transactionId: 'db739693-9b4a-4669-9945-8eab938783cc' @@ -209,7 +209,50 @@ const VALID_BIDDER_REQUEST = { params: { placementId: '13144370', siteId: '23434', - publisherId: '7689670753' + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, +} + +const VALID_GDPR_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', + }, + } + ], + refererInfo: { + page: 'test-page', + domain: 'test-domain', + ref: 'test-referer' + }, + gdprConsent: { + consentString: 'test-gdpr-string' + } +} + +const VALID_USP_BIDDER_REQUEST = { + auctionId: '19c97f22-5bd1-4b16-a128-80f75fb0a8a0', + bidderCode: 'aidem', + bidderRequestId: '1bbb7854dfa0d8', + bids: [ + { + params: { + placementId: '13144370', + siteId: '23434', + publisherId: '7689670753', }, } ], @@ -218,6 +261,7 @@ const VALID_BIDDER_REQUEST = { domain: 'test-domain', ref: 'test-referer' }, + uspConsent: '1YYY' } const SERVER_RESPONSE_BANNER = { @@ -411,7 +455,7 @@ describe('Aidem adapter', () => { expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_BANNER_REQUESTS.length) expect(data.imp[0]).to.be.a('object').that.has.all.keys( - 'banner', 'id', 'tagId' + 'banner', 'id', 'tagId', 'secure' ) expect(data.imp[0].banner).to.be.a('object').that.has.all.keys( 'format', 'topframe' @@ -427,7 +471,7 @@ describe('Aidem adapter', () => { expect(data.imp).to.be.a('array').that.has.lengthOf(DEFAULT_VALID_VIDEO_REQUESTS.length) expect(data.imp[0]).to.be.a('object').that.has.all.keys( - 'video', 'id', 'tagId' + 'video', 'id', 'tagId', 'secure' ) expect(data.imp[0].video).to.be.a('object').that.has.all.keys( 'mimes', 'minduration', 'maxduration', 'protocols', 'w', 'h' @@ -601,47 +645,51 @@ describe('Aidem adapter', () => { }); it(`should set gdpr to true`, function () { - config.setConfig({ - consentManagement: { - gdpr: { - // any data here set gdpr to true - }, - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // gdpr: { + // consentData: { + // getTCData: { + // tcString: 'test-gdpr-string' + // } + // } + // }, + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_GDPR_BIDDER_REQUEST); expect(data.regs.gdpr_applies).to.equal(true) }); it(`should set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'static', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); - const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'static', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); + const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_USP_BIDDER_REQUEST); expect(data.regs.us_privacy).to.equal('1YYY') }); it(`should not set usp_consent string`, function () { - config.setConfig({ - consentManagement: { - usp: { - cmpApi: 'iab', - consentData: { - getUSPData: { - uspString: '1YYY' - } - } - } - } - }); + // config.setConfig({ + // consentManagement: { + // usp: { + // cmpApi: 'iab', + // consentData: { + // getUSPData: { + // uspString: '1YYY' + // } + // } + // } + // } + // }); const { data } = spec.buildRequests(DEFAULT_VALID_BANNER_REQUESTS, VALID_BIDDER_REQUEST); expect(data.regs.us_privacy).to.undefined }); diff --git a/test/spec/modules/ajaBidAdapter_spec.js b/test/spec/modules/ajaBidAdapter_spec.js index dbc72d113f4..bd2bdd3e407 100644 --- a/test/spec/modules/ajaBidAdapter_spec.js +++ b/test/spec/modules/ajaBidAdapter_spec.js @@ -24,12 +24,12 @@ describe('AjaAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'asi': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/akamaiDapRtdProvider_spec.js b/test/spec/modules/akamaiDapRtdProvider_spec.js index 337fcf57a33..0787f911591 100644 --- a/test/spec/modules/akamaiDapRtdProvider_spec.js +++ b/test/spec/modules/akamaiDapRtdProvider_spec.js @@ -44,7 +44,7 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 503 + 'segtax': 708 } } @@ -56,7 +56,7 @@ describe('akamaiDapRtdProvider', function() { 'apiVersion': 'x1', 'domain': 'prebid.org', 'identityType': 'dap-signature:1.0.0', - 'segtax': 504 + 'segtax': 710 } } @@ -64,7 +64,7 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 503, + 'segtax': 708, 'identity': sampleIdentity } @@ -72,7 +72,7 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 'x1', 'domain': 'prebid.org', - 'segtax': 504, + 'segtax': 710, 'identity': sampleIdentity } let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds @@ -97,7 +97,7 @@ describe('akamaiDapRtdProvider', function() { const encRtdUserObj = { name: 'www.dataprovider3.com', ext: { - segtax: 504, + segtax: 710, taxonomyname: 'iab_audience_taxonomy' }, segment: [] @@ -262,13 +262,13 @@ describe('akamaiDapRtdProvider', function() { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 503 + 'segtax': 708 }; const encConfig = { 'api_hostname': 'prebid.dap.akadns.net', 'api_version': 1, 'domain': '', - 'segtax': 504 + 'segtax': 710 }; let identity = { type: 'dap-signature:1.0.0' @@ -396,7 +396,7 @@ describe('akamaiDapRtdProvider', function() { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - segtax: 503 + segtax: 708 }; expect(dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)).to.equal(undefined) const membership = {cohorts: ['1', '5', '7']} @@ -405,11 +405,11 @@ describe('akamaiDapRtdProvider', function() { }); describe('checkAndAddRealtimeData test', function () { - it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + it('add realtime data for segtax 708 and 710', function () { + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 708); expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); }); }); @@ -466,7 +466,7 @@ describe('akamaiDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); }); @@ -477,7 +477,7 @@ describe('akamaiDapRtdProvider', function() { let request = server.requests[0]; responseHeader['Akamai-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 504) + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); }); @@ -508,7 +508,7 @@ describe('akamaiDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503); + let rtdObj = dapUtils.dapGetRtdObj(membership, 708); expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); }); @@ -517,7 +517,7 @@ describe('akamaiDapRtdProvider', function() { dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503) + let rtdObj = dapUtils.dapGetRtdObj(membership, 708) expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); }); diff --git a/test/spec/modules/amxBidAdapter_spec.js b/test/spec/modules/amxBidAdapter_spec.js index 21fa2e2617c..5769afa1b2b 100644 --- a/test/spec/modules/amxBidAdapter_spec.js +++ b/test/spec/modules/amxBidAdapter_spec.js @@ -442,7 +442,6 @@ describe('AmxBidAdapter', () => { 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' }, @@ -583,6 +582,55 @@ describe('AmxBidAdapter', () => { expect(parsed).to.eql([]); }); + it('will read an bidderCode override from bid.ext.prebid.meta', () => { + const currentConfig = config.getConfig(); + config.setConfig({ + ...currentConfig, + bidderSettings: { + amx: { + allowAlternateBidderCodes: true + } + } + }); + + const parsed = spec.interpretResponse( + { body: { + ...sampleServerResponse, + r: { + [sampleRequestId]: [{ + ...sampleServerResponse.r[sampleRequestId][0], + b: [{ + ...sampleServerResponse.r[sampleRequestId][0].b[0], + ext: { + bc: 'amx-pmp', + ds: 'example', + } + }] + }] + }}}, + baseRequest + ); + + config.setConfig(currentConfig); + expect(parsed.length).to.equal(1); // we removed one + + // we should have display, video, display + expect(parsed[0]).to.deep.equal({ + ...baseBidResponse, + meta: { + ...baseBidResponse.meta, + mediaType: BANNER, + demandSource: 'example' + }, + mediaType: BANNER, + bidderCode: 'amx-pmp', + width: 300, + height: 600, // from the bid itself + ttl: 90, + ad: sampleDisplayAd, + }); + }); + it('can parse a display ad', () => { const parsed = spec.interpretResponse( { body: sampleServerResponse }, diff --git a/test/spec/modules/amxIdSystem_spec.js b/test/spec/modules/amxIdSystem_spec.js index c1ae2c791d5..b509ffe608b 100644 --- a/test/spec/modules/amxIdSystem_spec.js +++ b/test/spec/modules/amxIdSystem_spec.js @@ -1,6 +1,9 @@ import { amxIdSubmodule, storage } from 'modules/amxIdSystem.js'; import { server } from 'test/mocks/xhr.js'; import * as utils from 'src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const TEST_ID = '51b561e3-0d82-4aea-8487-093fffca4a3a'; const ERROR_CODES = [404, 501, 500, 403]; @@ -13,177 +16,198 @@ const config = { type: 'html5', }, }; +describe('AMX ID', () => { + describe('amxid submodule', () => { + it('should expose a "name" property containing amxId', () => { + expect(amxIdSubmodule.name).to.equal('amxId'); + }); -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); + 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 + 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); + 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; + describe('validateConfig', () => { + let logErrorSpy; - beforeEach(() => { - logErrorSpy = sinon.spy(utils, 'logError'); - }); - afterEach(() => { - logErrorSpy.restore(); - }); + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + afterEach(() => { + logErrorSpy.restore(); + }); - it('should allow configuration with no storage', () => { - expect( - amxIdSubmodule.getId( - { - ...config, - storage: undefined - }, - null, - null - ) - ).to.not.equal(undefined); - }); + it('should allow configuration with no storage', () => { + expect( + amxIdSubmodule.getId( + { + ...config, + storage: undefined + }, + null, + null + ) + ).to.not.equal(undefined); + }); - it('should return undefined if expires > 30', () => { - const expires = Math.floor(Math.random() * 90) + 30.01; - expect( - amxIdSubmodule.getId( - { - ...config, - storage: { - type: 'html5', - expires, + 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); + null, + null + ) + ).to.equal(undefined); + + expect(logErrorSpy.calledOnce).to.be.true; + expect(logErrorSpy.lastCall.lastArg).to.contain(expires); + }); }); -}); -describe('getId', () => { - const spy = sinon.spy(); + describe('getId', () => { + const spy = sinon.spy(); - beforeEach(() => { - spy.resetHistory(); - }); + beforeEach(() => { + spy.resetHistory(); + }); - it('should call the sync endpoint and accept a valid response', () => { - storage.setDataInLocalStorage('__amuidpb', TEST_ID); + it('should call the sync endpoint and accept a valid response', () => { + storage.setDataInLocalStorage('__amuidpb', TEST_ID); - const { callback } = amxIdSubmodule.getId(config, null, null); - callback(spy); + const { callback } = amxIdSubmodule.getId(config, null, null); + callback(spy); - const [request] = server.requests; - expect(request.withCredentials).to.be.true - expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) + const [request] = server.requests; + expect(request.withCredentials).to.be.true + expect(request.requestHeaders['Content-Type']).to.match(/text\/plain/) - const { search } = utils.parseUrl(request.url); - expect(search.av).to.equal(amxIdSubmodule.version); - expect(search.am).to.equal(TEST_ID); - expect(request.method).to.equal('GET'); + const { search } = utils.parseUrl(request.url); + expect(search.av).to.equal(amxIdSubmodule.version); + expect(search.am).to.equal(TEST_ID); + expect(request.method).to.equal('GET'); - request.respond( - 200, - {}, - JSON.stringify({ - id: TEST_ID, - v: '1.0a', - }) - ); + 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); - }); + 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); + 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, {}, ''); + 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); - }); + 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 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); + 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, {}, '{,,}'); + const [request] = server.requests; + request.respond(200, {}, '{,,}'); - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(undefined); - }); + 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.match(new RegExp(`^${intermediateValue}\?`)); - secondRequest.respond( - 200, - {}, - JSON.stringify({ - id: TEST_ID, - }) - ); - - expect(spy.calledOnce).to.be.true; - expect(spy.lastCall.lastArg).to.equal(TEST_ID); + 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.match(new RegExp(`^${intermediateValue}\?`)); + secondRequest.respond( + 200, + {}, + JSON.stringify({ + id: TEST_ID, + }) + ); + + expect(spy.calledOnce).to.be.true; + expect(spy.lastCall.lastArg).to.equal(TEST_ID); + }); }); -}); + describe('eid', () => { + before(() => { + attachIdSystem(amxIdSubmodule); + }); + it('amxId', () => { + const id = 'c4bcadb0-124f-4468-a91a-d3d44cf311c5' + const userId = { + amxId: id + }; + + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'amxdt.net', + uids: [{ + atype: 1, + id, + }] + }); + }); + }) +}) diff --git a/test/spec/modules/anPspParamsConverter_spec.js b/test/spec/modules/anPspParamsConverter_spec.js new file mode 100644 index 00000000000..a96730088b8 --- /dev/null +++ b/test/spec/modules/anPspParamsConverter_spec.js @@ -0,0 +1,133 @@ +import { expect } from 'chai'; + +import {convertAnParams} from '../../../modules/anPspParamsConverter'; +import { config } from '../../../src/config.js'; +import { deepClone } from '../../../src/utils'; +import adapterManager from '../../../src/adapterManager.js'; + +describe('anPspParamsConverter', function () { + let configStub; + let resolveAliasStub; + let didHookRun = false; + + const bidderRequests = [{ + bidderCode: 'appnexus', + bids: [{ + bidder: 'appnexus', + src: 's2s', + params: { + member: 958, + invCode: 12345, + placementId: '10001', + keywords: { + music: 'rock', + genre: ['80s', '90s'] + }, + publisherId: '111', + use_payment_rule: true + } + }] + }]; + + beforeEach(function () { + configStub = sinon.stub(config, 'getConfig'); + resolveAliasStub = sinon.stub(adapterManager, 'resolveAlias').callsFake(function (tarBidder) { + return (tarBidder === 'rubicon') ? 'rubicon' : 'appnexus'; + }); + }); + + afterEach(function () { + didHookRun = false; + configStub.restore(); + resolveAliasStub.restore(); + }); + + it('does not modify params when appnexus is not in s2sconfig', function () { + configStub.callsFake(function () { + return { + bidders: ['rubicon'] + }; + }); + + const testBidderRequests = deepClone(bidderRequests); + + convertAnParams(function () { + didHookRun = true; + }, testBidderRequests); + + expect(didHookRun).to.equal(true); + const resultParams = testBidderRequests[0].bids[0].params; + expect(resultParams.member).to.equal(958); + expect(resultParams.invCode).to.equal(12345); + expect(resultParams.placementId).to.equal('10001'); + expect(resultParams.keywords).to.deep.equal({ + music: 'rock', + genre: ['80s', '90s'] + }); + expect(resultParams.publisherId).to.equal('111'); + expect(resultParams.use_payment_rule).to.equal(true); + }); + + const tests = [{ + testName: 'modifies params when appnexus is the bidder', + fakeConfigFn: function () { + return { + bidders: ['appnexus'] + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + + return testBidderRequests; + } + }, { + testName: 'modifies params when a registered appnexus alias is used', + fakeConfigFn: function () { + return { + bidders: ['beintoo'] + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + testBidderRequests.bidderCode = 'beintoo'; + testBidderRequests[0].bids[0].bidder = 'beintoo'; + + return testBidderRequests; + } + }, { + testName: 'modifies params when pbjs.aliasBidder alias is used', + fakeConfigFn: function () { + return { + bidders: ['aliasBidderTest'], + }; + }, + applyBidderRequestChanges: function () { + const testBidderRequests = deepClone(bidderRequests); + testBidderRequests.bidderCode = 'aliasBidderTest'; + testBidderRequests[0].bids[0].bidder = 'aliasBidderTest'; + + return testBidderRequests; + } + }]; + + tests.forEach((testCfg) => { + it(testCfg.testName, function () { + configStub.callsFake(testCfg.fakeConfigFn); + + const testBidderRequests = testCfg.applyBidderRequestChanges(); + + convertAnParams(function () { + didHookRun = true; + }, testBidderRequests); + + expect(didHookRun).to.equal(true); + const resultParams = testBidderRequests[0].bids[0].params; + expect(resultParams.member).to.equal('958'); + expect(resultParams.inv_code).to.equal('12345'); + expect(resultParams.placement_id).to.equal(10001); + expect(resultParams.keywords).to.equal('music=rock,genre=80s,genre=90s'); + expect(resultParams.publisher_id).to.equal(111); + expect(resultParams.use_pmt_rule).to.equal(true); + }); + }); +}); diff --git a/test/spec/modules/aniviewBidAdapter_spec.js b/test/spec/modules/aniviewBidAdapter_spec.js index a9498af046c..726bccaa027 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -2,267 +2,401 @@ import { spec } from 'modules/aniviewBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; const { expect } = require('chai'); -describe('ANIVIEW Bid Adapter Test', function () { +const PUBLISHER_ID_1 = 'publisher_id_1'; +const CHANNEL_ID_1 = 'channel_id_1'; +const PUBLISHER_ID_2 = 'publisher_id_2'; +const CHANNEL_ID_2 = 'channel_id_2'; +const BID_ID_1 = 'bid_id_1'; +const BID_ID_2 = 'bid_id_2'; +const BIDDER_REQUEST_ID = 'bidder_request_id'; +const CUSTOM_DOMAIN = 'example.com'; + +const BASE_URL = 'https://' + CUSTOM_DOMAIN + '/track' + + '?rtbbp=10' + + '&cpm=${AUCTION_PRICE}' + + '&aucid=${AUCTION_ID}' + + '&aucbid=${AUCTION_BID_ID}' + + '&limid=${AUCTION_IMP_ID}' + + '&aucseid=${AUCTION_SEAT_ID}' + + '&aucadid=${AUCTION_AD_ID}'; +const LURL = `${BASE_URL}&e=AV_M40&rcd=\${AUCTION_LOSS}`; +const NURL = `${BASE_URL}&e=AV_M4`; + +const VIDEO_VAST = `` +const BANNER_VAST = VIDEO_VAST; +const BANNER_HTML = '

HTML BANNER

'; + +const CURRENCY = 'USD'; +const PRICE = 10; +const FLOOR_PRICE = PRICE * 0.5; +const TTL = 600; + +const VIDEO_SIZE = { width: 640, height: 360 }; +const BANNER_SIZE = { width: 250, height: 250 }; + +const CUSTOM_RENDERER_URL = `https://${CUSTOM_DOMAIN}/script/6.1/prebidRenderer.js`; +const DEFAULT_RENDERER_URL = `https://player.aniview.com/script/6.1/prebidRenderer.js`; + +const MOCK = { + bidRequest: () => ({ + bidderCode: 'aniview', + auctionId: null, + bidderRequestId: BIDDER_REQUEST_ID, + bids: [ + { + bidder: 'aniview', + params: { + AV_PUBLISHERID: PUBLISHER_ID_1, + AV_CHANNELID: CHANNEL_ID_1, + playerDomain: CUSTOM_DOMAIN, + }, + mediaTypes: { + video: { + playerSize: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + context: 'outstream', + mimes: ['video/mpeg', 'video/mp4', 'application/javascript'], + } + }, + bidId: BID_ID_1, + bidderRequestId: BIDDER_REQUEST_ID, + sizes: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + }, + { + bidder: 'aniview', + params: { + AV_PUBLISHERID: PUBLISHER_ID_2, + AV_CHANNELID: CHANNEL_ID_2, + playerDomain: CUSTOM_DOMAIN, + }, + mediaTypes: { + video: { + playerSize: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + context: 'outstream', + mimes: ['video/mpeg', 'video/mp4', 'application/javascript'], + } + }, + bidId: BID_ID_2, + bidderRequestId: BIDDER_REQUEST_ID, + sizes: [[VIDEO_SIZE.width, VIDEO_SIZE.height]], + floorData: { + currency: CURRENCY, + floor: FLOOR_PRICE, + }, + getFloor: _ => ({ + currency: CURRENCY, + floor: FLOOR_PRICE, + }), + }, + ], + auctionStart: 1722343584268, + timeout: 1_000, + start: 1722343584269, + ortb2: { + source: {}, + site: { + page: 'http://localhost:8080/', + ref: 'http://localhost:8080/', + domain: 'http://localhost:8080', + publisher: { + domain: 'http://localhost:8080', + } + }, + device: { + w: 1800, + h: 1169, + language: 'en', + }, + }, + }), + + bidderResponse: () => ({ + body: { + id: 'bidder_response_id', + bidid: 'bidder_response_bid_id', + cur: CURRENCY, + ext: { + aniview: { + sync: [ + { url: 'https://iframe-1.example.com/sync', e: 'sync', pr: '14', t: 3 }, + { url: 'https://iframe-2.example.com/sync', e: 'sync', pr: '28', t: 3 }, + { url: 'https://image.example.com/sync', e: 'sync', pr: 'abc12', t: 1 } + ] + } + }, + seatbid: [ + { + seat: '', + bid: [ + { + adm: VIDEO_VAST, + adomain: [''], + id: 'seatbid_bid_id_1', + impid: BID_ID_1, + lurl: LURL, + nurl: NURL, + price: PRICE, + } + ] + } + ] + }, + }) +} + +describe('Aniview Bid Adapter', function () { const adapter = newBidder(spec); - describe('inherited functions', function () { + describe('Inherited function', function () { it('exists and is a function', function () { expect(adapter.callBids).to.exist.and.to.be.a('function'); }); }); describe('isBidRequestValid', function () { - let bid = { - 'bidder': 'aniview', - '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); + let videoBidRequest; + + beforeEach(() => { + videoBidRequest = MOCK.bidRequest(); }); - 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); + it('should return `true` when required params found', function () { + expect(spec.isBidRequestValid(videoBidRequest.bids[0])).to.be.true; + }); + + it('should return `false` when required params are wrong', function () { + videoBidRequest.bids[0].params = { something: 'is wrong' }; + + expect(spec.isBidRequestValid(videoBidRequest.bids[0])).to.be.false; }); }); describe('buildRequests', function () { - let bid2Requests = [ - { - 'bidder': 'aniview', - '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': 'aniview', - '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://gov.aniview.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://gov.aniview.com/api/adserver/vast3/'); + let videoBidRequest; + + beforeEach(() => { + videoBidRequest = MOCK.bidRequest(); }); - 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://gov.aniview.com/api/adserver/vast3/'); + it('should return expected request object', function () { + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + + expect(bidRequest).to.exist.and.to.be.a('array').and.to.have.lengthOf(2); + + const { url, method, data } = bidRequest[0]; + const { ext, imp } = data; + + expect(url).equal('https://rtb.aniview.com/sspRTB2'); + expect(method).equal('POST'); + expect(imp[0].tagid).equal(CHANNEL_ID_1); + expect(imp[0].id).equal(videoBidRequest.bids[0].bidId); + expect(ext.aniview.pbjs).equal(1); }); - }); - describe('interpretResponse', function () { - let bidRequest = { - 'url': 'https://gov.aniview.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 = 'aniview'; - 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.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'); - expect(bidResponse.meta.advertiserDomains).to.be.an('array').that.is.empty; + it('should have floor data inside imp', function () { + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + const imp = bidRequest[1].data.imp[0]; + + expect(imp.bidfloor).equal(FLOOR_PRICE); + expect(imp.bidfloorcur).equal(CURRENCY); }); - it('safely handles XML parsing failure from invalid bid response', function () { - let invalidServerResponse = {}; - invalidServerResponse.body = ''; + it('should not have floor data in imp if getFloor returns empty object', function () { + videoBidRequest.bids[1].getFloor = () => ({}); + + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + const imp = bidRequest[1].data.imp[0]; - let result = spec.interpretResponse(invalidServerResponse, bidRequest); - expect(result.length).to.equal(0); + expect(imp.bidfloor).not.exist; + expect(imp.bidfloorcur).not.exist; }); - it('handles nobid responses', function () { - let nobidResponse = {}; - nobidResponse.body = ''; + it('should use dev environment', function () { + const DEV_ENDPOINT = 'https://dev.aniview.com/sspRTB2'; + videoBidRequest.bids[0].params.dev = { endpoint: DEV_ENDPOINT }; - let result = spec.interpretResponse(nobidResponse, bidRequest); - expect(result.length).to.equal(0); + const bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + + expect(bidRequest[0].url).to.equal(DEV_ENDPOINT); }); + }); - 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) + describe('interpretResponse', function () { + describe('Video format', function () { + let bidRequests, bidderResponse; + + beforeEach(function() { + const videoBidRequest = MOCK.bidRequest(); + + bidRequests = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + bidderResponse = MOCK.bidderResponse(); + }); + + it('should return empty bids array for empty response', function () { + const emptyResponse = bidderResponse.body = {}; + const bids = spec.interpretResponse(emptyResponse, bidRequests[0]); + + expect(bids).to.be.empty; + }); + + it('should return valid bids array', function () { + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + const bid = bids[0]; + + expect(bids.length).to.greaterThan(0); + expect(bid.vastXml).to.exist.and.to.not.have.string('${AUCTION_PRICE}'); + expect(bid.vastXml).to.have.string('cpm=' + PRICE); + expect(bid.vastUrl).to.exist.and.to.not.have.string('${AUCTION_PRICE}'); + expect(bid.vastUrl).to.have.string('cpm=' + PRICE); + expect(bid.requestId).to.equal(bidRequests[0].data.imp[0].id); + expect(bid.cpm).to.equal(PRICE); + expect(bid.ttl).to.equal(TTL); + expect(bid.currency).to.equal(CURRENCY); + expect(bid.netRevenue).to.equal(true); + expect(bid.mediaType).to.equal('video'); + expect(bid.meta.advertiserDomains).to.be.an('array'); + expect(bid.creativeId).to.exist; + expect(bid.width).to.exist; + expect(bid.height).to.exist; + }); + + it('should return bid without required properties if cpm less or equal 0', function () { + bidderResponse.body.seatbid[0].bid[0].price = 0; + + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + const bid = bids[0]; + + expect(bid.width).to.not.exist; + expect(bid.height).to.not.exist; + expect(bid.creativeId).to.not.exist; + }); + + it('should return empty bids array if no bids in response', function () { + bidderResponse.body.seatbid[0].bid = []; + + const bids = spec.interpretResponse(bidderResponse, bidRequests[0]); + + expect(bids).to.exist.and.to.be.a('array').and.to.have.lengthOf(0); + }); + + it('should add renderer if outstream context', function () { + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.renderer.url).to.equal(CUSTOM_RENDERER_URL); + expect(bid.renderer.config.AV_PUBLISHERID).to.equal(PUBLISHER_ID_1); + expect(bid.renderer.config.AV_CHANNELID).to.equal(CHANNEL_ID_1); + expect(bid.renderer.loaded).to.equal(false); + expect(bid.width).to.equal(VIDEO_SIZE.width); + expect(bid.height).to.equal(VIDEO_SIZE.height); + }); + + it('should use default renderer domain', function () { + delete bidRequests[0].bids[0].params.playerDomain; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.renderer.url).to.equal(DEFAULT_RENDERER_URL); + }); + + it('should not add renderer if context is not outstream', function () { + bidRequests[0].bids[0].mediaTypes.video.context = 'instream'; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bidRequests[0].bids[0].mediaTypes.video.context).to.be.not.equal('outstream'); + expect(bid.renderer).to.not.exist; + }); }); - 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] + describe('Banner format', function () { + let bidRequests, bidderResponse; - 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) - }) + beforeEach(function() { + const bannerBidRequest = MOCK.bidRequest(); + + // Converting video bid request to banner bid request + + delete bannerBidRequest.bids[0].mediaTypes.video; + + bannerBidRequest.bids[0].sizes = [[BANNER_SIZE.width, BANNER_SIZE.height]]; + bannerBidRequest.bids[0].mediaTypes.banner = { + sizes: [ + [BANNER_SIZE.width, BANNER_SIZE.height], + [BANNER_SIZE.width * 2, BANNER_SIZE.height], + ], + }; + + bidRequests = spec.buildRequests(bannerBidRequest.bids, bannerBidRequest); + bidderResponse = MOCK.bidderResponse(); + }); + + it('should return valid banner bids (HTML)', function () { + bidderResponse.body.seatbid[0].bid[0].adm = BANNER_HTML; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.ad).to.exist; + expect(bid.cpm).to.equal(PRICE); + expect(bid.width).to.equal(BANNER_SIZE.width); + expect(bid.height).to.equal(BANNER_SIZE.height); + expect(bid.renderer).to.not.exist; + }); + + it('should return valid banner bids (VAST) with renderer', function () { + bidderResponse.body.seatbid[0].bid[0].adm = BANNER_VAST; + + const bid = spec.interpretResponse(bidderResponse, bidRequests[0])[0]; + + expect(bid.ad).to.exist; + expect(bid.ad).to.not.have.string('${AUCTION_PRICE}'); + expect(bid.ad).to.have.string('cpm=' + PRICE); + expect(bid.cpm).to.equal(PRICE); + expect(bid.width).to.equal(BANNER_SIZE.width); + expect(bid.height).to.equal(BANNER_SIZE.height); + expect(bid.renderer.url).to.equal(CUSTOM_RENDERER_URL); + expect(bid.renderer.config.AV_PUBLISHERID).to.equal(PUBLISHER_ID_1); + expect(bid.renderer.config.AV_CHANNELID).to.equal(CHANNEL_ID_1); + expect(bid.renderer.loaded).to.equal(false); + }); + }); }); describe('getUserSyncs', function () { - 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 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('iframe'); + let bidRequest, bidderResponse; + + beforeEach(function() { + const videoBidRequest = MOCK.bidRequest(); + + bidRequest = spec.buildRequests(videoBidRequest.bids, videoBidRequest); + bidderResponse = MOCK.bidderResponse(); }); - 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'); + it('should get syncs from response', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(3); + }); + + it('should get only pixel syncs from response', function () { + const syncs = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(1); + expect(syncs[0].type).to.equal('image'); + }); + + it('should return empty array of syncs if no syncs in response', function () { + delete bidderResponse.body.ext.aniview.sync + + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: false }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(0); + }); + + it('should return empty array of syncs if no body in response', function () { + delete bidderResponse.body + + const syncs = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }, [bidderResponse]); + + expect(syncs).to.be.a('array').and.to.have.lengthOf(0); }); }); }); diff --git a/test/spec/modules/anyclipBidAdapter_spec.js b/test/spec/modules/anyclipBidAdapter_spec.js index 3de36f9fe06..9f34184d378 100644 --- a/test/spec/modules/anyclipBidAdapter_spec.js +++ b/test/spec/modules/anyclipBidAdapter_spec.js @@ -1,160 +1,458 @@ -import { expect } from 'chai'; -import { spec } from 'modules/anyclipBidAdapter.js'; +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/anyclipBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; -describe('anyclipBidAdapter', function () { - afterEach(function () { - global._anyclip = undefined; - }); +const ENDPOINT = 'https://prebid.anyclip.com'; - let bid; - - function mockBidRequest() { - return { - mediaTypes: { - banner: { - sizes: [ - [300, 250], - [728, 90], - [468, 60] - ] - } - }, - bidder: 'anyclip', - params: { - publisherId: '12345', - supplyTagId: '-mptNo0BycUG4oCDgGrU' - } - }; - }; +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'anyclip', + params: { + publisherId: 'anyclip', + supplyTagId: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; +const videoBidderRequest = { + bidderCode: 'anyclip', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'anyclip', + bids: [{bidId: 'qwerty'}] +}; + +describe('anyclipBidAdapter', () => { describe('isBidRequestValid', function () { - this.beforeEach(function () { - bid = mockBidRequest(); + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return true if all required fields are present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('should return false when required publisherId param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.publisherId; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if bidder does not correspond', function () { - bid.bidder = 'abc'; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return false when required supplyTagId param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.supplyTagId; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if params object is missing', function () { - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false if publisherId is missing from params', function () { - delete bid.params.publisherId; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); }); - it('should return false if supplyTagId is missing from params', function () { - delete bid.params.supplyTagId; - expect(spec.isBidRequestValid(bid)).to.be.false; + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); }); - it('should return false if mediaTypes is missing', function () { - delete bid.mediaTypes; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); }); - it('should return false if banner is missing from mediaTypes ', function () { - delete bid.mediaTypes.banner; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + publisherId: 'anyclip', + supplyTagId: '40', + floor: null + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); }); - it('should return false if sizes is missing from banner object', function () { - delete bid.mediaTypes.banner.sizes; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); }); - it('should return false if sizes is not an array', function () { - bid.mediaTypes.banner.sizes = 'test'; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); }); - it('should return false if sizes is an empty array', function () { - bid.mediaTypes.banner.sizes = []; - expect(spec.isBidRequestValid(bid)).to.be.false; + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); }); - }); - describe('buildRequests', function () { - let bidderRequest = { - refererInfo: { - page: 'http://example.com', - domain: 'example.com', - }, - timeout: 3000 - }; - - this.beforeEach(function () { - bid = mockBidRequest(); - Object.assign(bid, { - adUnitCode: '1', - transactionId: '123', - sizes: bid.mediaTypes.banner.sizes + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } }); }); - it('when pubtag is not available, return undefined', function () { - expect(spec.buildRequests([bid], bidderRequest)).to.undefined; + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0].env; + expect(request).to.have.property('floor').and.to.equal(5); }); - it('when pubtag is available, creates a ServerRequest object with method, URL and data', function() { - global._anyclip = { - PubTag: function() {}, - pubTag: { - requestBids: function() {} + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' } }; - expect(spec.buildRequests([bid], bidderRequest)).to.exist; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); }); }); - describe('interpretResponse', function() { - it('should return an empty array when parsing a no bid response', function () { - const response = {}; - const request = {}; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(0); - }); - it('should return bids array', function() { - const response = {}; - const request = { - bidRequest: { - bidId: 'test-bidId', - transactionId: '123' + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['anyclip'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['anyclip']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] } }; - global._anyclip = { - PubTag: function() {}, - pubTag: { - getBids: function(transactionId) { - return { - adServer: { - bid: { - ad: 'test-ad', - creativeId: 'test-crId', - meta: { - advertiserDomains: ['anyclip.com'] - }, - width: 300, - height: 250, - ttl: 300 - } - }, - cpm: 1.23, + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] } - } + }] } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + 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: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].currency).to.equal('USD'); - expect(bids[0].width).to.equal(300); - expect(bids[0].height).to.equal(250); - expect(bids[0].ad).to.equal('test-ad'); - expect(bids[0].ttl).to.equal(300); - expect(bids[0].creativeId).to.equal('test-crId'); - expect(bids[0].netRevenue).to.false; - expect(bids[0].meta.advertiserDomains[0]).to.equal('anyclip.com'); + const result = getBidFloor(bid); + expect(result).to.equal(5); }); }); -}); +}) diff --git a/test/spec/modules/apacdexBidAdapter_spec.js b/test/spec/modules/apacdexBidAdapter_spec.js index 98d07575ee7..d005934d062 100644 --- a/test/spec/modules/apacdexBidAdapter_spec.js +++ b/test/spec/modules/apacdexBidAdapter_spec.js @@ -201,8 +201,7 @@ describe('ApacdexBidAdapter', function () { }, 'bidder': 'apacdex', 'params': { - 'siteId': '1a2b3c4d5e6f1a2b3c4d', - 'geo': { 'lat': 123.13123456, 'lon': 54.23467311, 'accuracy': 60 } + 'siteId': '1a2b3c4d5e6f1a2b3c4d' }, 'adUnitCode': 'adunit-code-1', 'sizes': [[300, 250], [300, 600]], @@ -321,10 +320,6 @@ describe('ApacdexBidAdapter', function () { const bidRequests = spec.buildRequests(bidRequest, bidderRequests); expect(bidRequests.data.eids).to.deep.equal(bidRequest[0].userIdAsEids) }); - it('should fail to return a properly formatted request with geo defined', function () { - const bidRequests = spec.buildRequests(bidRequest, bidderRequests); - expect(bidRequests.data.geo).to.not.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'); diff --git a/test/spec/modules/appierBidAdapter_spec.js b/test/spec/modules/appierBidAdapter_spec.js index 8b6ad5c2f6f..0ad14b1ec61 100644 --- a/test/spec/modules/appierBidAdapter_spec.js +++ b/test/spec/modules/appierBidAdapter_spec.js @@ -30,17 +30,17 @@ describe('AppierAdapter', function () { }); it('should return false when required param zoneId is missing', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required param zoneId has wrong type', function () { - let bid = Object.assign({}, bid); - bid.params = { + let invalidBid = Object.assign({}, bid); + invalidBid.params = { 'hzid': null }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/appnexusBidAdapter_spec.js b/test/spec/modules/appnexusBidAdapter_spec.js index c2da2f36223..b2bdfbcdf5a 100644 --- a/test/spec/modules/appnexusBidAdapter_spec.js +++ b/test/spec/modules/appnexusBidAdapter_spec.js @@ -1,7 +1,6 @@ import { expect } from 'chai'; import { spec } from 'modules/appnexusBidAdapter.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 * as utils from 'src/utils.js'; @@ -76,21 +75,21 @@ describe('AppNexusAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement_id': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -432,6 +431,42 @@ describe('AppNexusAdapter', function () { expect(payload.tags[0].video_frameworks).to.deep.equal([1, 4]) }); + it('should convert and include ORTB2 device data when available', function () { + const bidRequest = deepClone(bidRequests[0]); + const bidderRequest = { + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }; + + const expectedDeviceResult = { + useragent: bidderRequest.ortb2.device.ua, + devicetype: 'Mobile/Tablet - General', + make: bidderRequest.ortb2.device.make, + model: bidderRequest.ortb2.device.model, + os: bidderRequest.ortb2.device.os, + os_version: bidderRequest.ortb2.device.osv, + w: bidderRequest.ortb2.device.w, + h: bidderRequest.ortb2.device.h, + }; + + const request = spec.buildRequests([bidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(expectedDeviceResult); + }); + it('should add video property when adUnit includes a renderer', function () { const videoData = { mediaTypes: { @@ -1996,45 +2031,47 @@ describe('AppNexusAdapter', function () { } if (FEATURES.NATIVE) { + const BASE_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': '', + 'video': { + 'content': '' + } + }; + 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': '', - 'video': { - 'content': '' - } - }; + response1.tags[0].ads[0].rtb.native = BASE_NATIVE; let bidderRequest = { bids: [{ bidId: '3db3773286ee59', @@ -2045,10 +2082,236 @@ describe('AppNexusAdapter', function () { 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.body2).to.equal('Additional body text'); expect(result[0].native.cta).to.equal('Do it'); expect(result[0].native.image.url).to.equal('https://cdn.adnxs.com/img.png'); + // Video is technically not a base Prebid native field, so it should be included as part of the ext + // But it's also included here for backwards compatibility if people read the bid directly expect(result[0].native.video.content).to.equal(''); }); + + it('handles custom native fields as ext', function () { + let response1 = deepClone(response); + response1.tags[0].ads[0].ad_type = 'native'; + response1.tags[0].ads[0].rtb.native = { + ...BASE_NATIVE, + // 'video' is included in base native + 'title1': 'Custom Title 1', + 'title2': 'Custom Title 2', + 'title3': 'Custom Title 3', + 'title4': 'Custom Title 4', + 'title5': 'Custom Title 5', + // Not to be confused with Prebid's base native body & body2 + 'body1': 'Custom Body 1', + 'body2': 'Custom Body 2', + 'body3': 'Custom Body 3', + 'body4': 'Custom Body 4', + 'body5': 'Custom Body 5', + 'image1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_1.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_2.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_3.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_4.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'image5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_5.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'icon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'icon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialicon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'socialurl1': 'https://www.xandr.com/platform/monetize/#socialUrl1', + 'socialurl2': 'https://www.xandr.com/platform/monetize/#socialUrl2', + 'socialurl3': 'https://www.xandr.com/platform/monetize/#socialUrl3', + 'socialurl4': 'https://www.xandr.com/platform/monetize/#socialUrl4', + 'socialurl5': 'https://www.xandr.com/platform/monetize/#socialUrl5', + 'displayurl1': 'https://www.xandr.com/platform/monetize/#displayUrl1', + 'displayurl2': 'https://www.xandr.com/platform/monetize/#displayUrl2', + 'displayurl3': 'https://www.xandr.com/platform/monetize/#displayUrl3', + 'displayurl4': 'https://www.xandr.com/platform/monetize/#displayUrl4', + 'displayurl5': 'https://www.xandr.com/platform/monetize/#displayUrl5', + 'ctatext1': 'Custom CTA 1', + 'ctatext2': 'Custom CTA 2', + 'ctatext3': 'Custom CTA 3', + 'ctatext4': 'Custom CTA 4', + 'ctatext5': 'Custom CTA 5', + }; + let bidderRequest = { + bids: [{ + bidId: '3db3773286ee59', + adUnitCode: 'code' + }] + } + + let result = spec.interpretResponse({ body: response1 }, { bidderRequest }); + expect(result[0].native.ext).to.deep.equal({ + 'video': { + 'content': '' + }, + 'customTitle1': 'Custom Title 1', + 'customTitle2': 'Custom Title 2', + 'customTitle3': 'Custom Title 3', + 'customTitle4': 'Custom Title 4', + 'customTitle5': 'Custom Title 5', + 'customBody1': 'Custom Body 1', + 'customBody2': 'Custom Body 2', + 'customBody3': 'Custom Body 3', + 'customBody4': 'Custom Body 4', + 'customBody5': 'Custom Body 5', + 'customImage1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_1.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_2.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_3.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_4.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customImage5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/custom_image_5.jpg?[fullhash]', + 'height': 627, + 'width': 1200, + }, + 'customIcon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customIcon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon1': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon2': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon3': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon4': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialIcon5': { + 'url': 'https://monetize.xandr.com/creative-ui/assets/logo.jpg?[fullhash]', + 'height': 128, + 'width': 128, + }, + 'customSocialUrl1': 'https://www.xandr.com/platform/monetize/#socialUrl1', + 'customSocialUrl2': 'https://www.xandr.com/platform/monetize/#socialUrl2', + 'customSocialUrl3': 'https://www.xandr.com/platform/monetize/#socialUrl3', + 'customSocialUrl4': 'https://www.xandr.com/platform/monetize/#socialUrl4', + 'customSocialUrl5': 'https://www.xandr.com/platform/monetize/#socialUrl5', + 'customDisplayUrl1': 'https://www.xandr.com/platform/monetize/#displayUrl1', + 'customDisplayUrl2': 'https://www.xandr.com/platform/monetize/#displayUrl2', + 'customDisplayUrl3': 'https://www.xandr.com/platform/monetize/#displayUrl3', + 'customDisplayUrl4': 'https://www.xandr.com/platform/monetize/#displayUrl4', + 'customDisplayUrl5': 'https://www.xandr.com/platform/monetize/#displayUrl5', + 'customCta1': 'Custom CTA 1', + 'customCta2': 'Custom CTA 2', + 'customCta3': 'Custom CTA 3', + 'customCta4': 'Custom CTA 4', + 'customCta5': 'Custom CTA 5', + }); + }); } if (FEATURES.VIDEO) { @@ -2151,54 +2414,73 @@ describe('AppNexusAdapter', function () { }); }); - describe('transformBidParams', function () { - let gcStub; - let adUnit = { bids: [{ bidder: 'appnexus' }] }; ; - - before(function () { - gcStub = sinon.stub(config, 'getConfig'); + describe('getUserSyncs', function() { + let syncOptions, gdprConsent; + + beforeEach(() => { + gdprConsent = { + gdprApplies: true, + consentString: 'CPJl4C8PJl4C8OoAAAENAwCMAP_AAH_AAAAAAPgAAAAIAPgAAAAIAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', + vendorData: { + purpose: { + consents: { + '1': true + } + } + } + } }); - after(function () { - gcStub.restore(); + describe('pixel', function () { + beforeEach(() => { + syncOptions = { pixelEnabled: true }; + }); + + it('pixelEnabled on', function () { + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('image'); + expect(result[0].url).to.equal('https://px.ads.linkedin.com/setuid?partner=appNexus'); + }); + + it('pixelEnabled off', function () { + syncOptions.pixelEnabled = false; + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); }); - it('convert keywords param differently for psp endpoint with single s2sConfig', function () { - gcStub.withArgs('s2sConfig').returns({ - bidders: ['appnexus'], - endpoint: { - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - } + describe('iframe', function () { + beforeEach(() => { + syncOptions = { iframeEnabled: true }; }); - const oldParams = { - keywords: { - genre: ['rock', 'pop'], - pets: 'dog' - } - }; + it('iframeEnabled on with gdpr purpose 1 on', function () { + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('https://acdn.adnxs.com/dmp/async_usersync.html'); + }); - const newParams = spec.transformBidParams(oldParams, true, adUnit); - expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); - }); + it('iframeEnabled on with gdpr purpose1 off', function () { + gdprConsent.vendorData.purpose.consents['1'] = false - it('convert keywords param differently for psp endpoint with array s2sConfig', function () { - gcStub.withArgs('s2sConfig').returns([{ - bidders: ['appnexus'], - endpoint: { - p1Consent: 'https://ib.adnxs.com/openrtb2/prebid' - } - }]); + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); - const oldParams = { - keywords: { - genre: ['rock', 'pop'], - pets: 'dog' - } - }; + it('iframeEnabled on without gdpr', function () { + const result = spec.getUserSyncs(syncOptions, [], null, null); + expect(result).to.have.length(1); + expect(result[0].type).to.equal('iframe'); + expect(result[0].url).to.equal('https://acdn.adnxs.com/dmp/async_usersync.html'); + }); - const newParams = spec.transformBidParams(oldParams, true, adUnit); - expect(newParams.keywords).to.equal('genre=rock,genre=pop,pets=dog'); + it('iframeEnabled off', function () { + syncOptions.iframeEnabled = false; + const result = spec.getUserSyncs(syncOptions, [], gdprConsent, null); + expect(result).to.be.undefined; + }); }); }); }); diff --git a/test/spec/modules/asealBidAdapter_spec.js b/test/spec/modules/asealBidAdapter_spec.js index 2dc1b47b7d0..900bda11390 100644 --- a/test/spec/modules/asealBidAdapter_spec.js +++ b/test/spec/modules/asealBidAdapter_spec.js @@ -87,10 +87,10 @@ describe('asealBidAdapter', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/asoBidAdapter_spec.js b/test/spec/modules/asoBidAdapter_spec.js index e317a8828e7..a37e4647c7a 100644 --- a/test/spec/modules/asoBidAdapter_spec.js +++ b/test/spec/modules/asoBidAdapter_spec.js @@ -6,7 +6,7 @@ import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; import {parseUrl} from '../../../src/utils'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; describe('Adserver.Online bidding adapter', function () { @@ -200,8 +200,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); @@ -229,8 +229,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); @@ -258,8 +258,8 @@ describe('Adserver.Online bidding adapter', function () { expect(payload.site.page).to.equal('https://example.com/page.html'); expect(payload.device).to.exist; - expect(payload.device.w).to.equal(window.innerWidth); - expect(payload.device.h).to.equal(window.innerHeight); + expect(payload.device.w).to.equal(window.screen.width); + expect(payload.device.h).to.equal(window.screen.height); expect(payload.imp).to.have.lengthOf(1); diff --git a/test/spec/modules/audiencerunBidAdapter_spec.js b/test/spec/modules/audiencerunBidAdapter_spec.js index 5c736345068..65349409e5e 100644 --- a/test/spec/modules/audiencerunBidAdapter_spec.js +++ b/test/spec/modules/audiencerunBidAdapter_spec.js @@ -60,22 +60,22 @@ describe('AudienceRun bid adapter tests', function () { }); it('should return true when zoneId is valid', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { zoneId: '12345abcde', }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; - bid.params = {}; + invalidBid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/axisBidAdapter_spec.js b/test/spec/modules/axisBidAdapter_spec.js index 083f05f5c0a..2db6c907851 100644 --- a/test/spec/modules/axisBidAdapter_spec.js +++ b/test/spec/modules/axisBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/axisBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'axis' +const bidder = 'axis'; describe('AxisBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -19,7 +29,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -35,7 +46,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -59,7 +71,8 @@ describe('AxisBidAdapter', function () { params: { integration: '000000', token: '000000' - } + }, + userIdAsEids } ]; @@ -78,15 +91,25 @@ describe('AxisBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, ortb2: { site: { cat: ['IAB24'] + }, + device: { + w: 1512, + h: 982, + language: 'en-UK' } - } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -121,6 +144,7 @@ describe('AxisBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -139,7 +163,7 @@ describe('AxisBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.iabCat).to.have.lengthOf(1); @@ -156,6 +180,7 @@ describe('AxisBidAdapter', function () { expect(placement.token).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -183,8 +208,10 @@ describe('AxisBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -199,12 +226,38 @@ describe('AxisBidAdapter', function () { 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); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -410,5 +463,17 @@ describe('AxisBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.axis-marketplace.com/image?pbjs=1&ccpa=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.axis-marketplace.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/azerionedgeRtdProvider_spec.js b/test/spec/modules/azerionedgeRtdProvider_spec.js index f08aaebdf55..0eef82a2512 100644 --- a/test/spec/modules/azerionedgeRtdProvider_spec.js +++ b/test/spec/modules/azerionedgeRtdProvider_spec.js @@ -8,11 +8,17 @@ describe('Azerion Edge RTD submodule', function () { { id: '1', visits: 123 }, { id: '2', visits: 456 }, ]; - + const IMPROVEDIGITAL_GVLID = '253'; const key = 'publisher123'; const bidders = ['appnexus', 'improvedigital']; const process = { key: 'value' }; const dataProvider = { name: 'azerionedge', waitForIt: true }; + const userConsent = {gdpr: {gdprApplies: 'gdpr-applies', consentString: 'consent-string'}, usp: 'usp'}; + + const resetAll = () => { + window.azerionPublisherAudiences.resetHistory(); + loadExternalScript.resetHistory(); + } let reqBidsConfigObj; let storageStub; @@ -33,7 +39,11 @@ describe('Azerion Edge RTD submodule', function () { let returned; beforeEach(function () { - returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider); + returned = azerionedgeRTD.azerionedgeSubmodule.init(dataProvider, userConsent); + }); + + it('should have the correct gvlid', () => { + expect(azerionedgeRTD.azerionedgeSubmodule.gvlid).to.equal(IMPROVEDIGITAL_GVLID); }); it('should return true', function () { @@ -49,18 +59,21 @@ describe('Azerion Edge RTD submodule', function () { expect(loadExternalScript.args[0][0]).to.deep.equal(expected); }); - it('should call azerionPublisherAudiencesStub with empty configuration', function () { - expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal({}); + [ + ['gdprApplies', userConsent.gdpr.gdprApplies], + ['gdprConsent', userConsent.gdpr.consentString], + ['uspConsent', userConsent.usp], + ].forEach(([key, value]) => { + it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value}); + }); }); describe('with key', function () { beforeEach(function () { - window.azerionPublisherAudiences.resetHistory(); - loadExternalScript.resetHistory(); - returned = azerionedgeRTD.azerionedgeSubmodule.init({ - ...dataProvider, - params: { key }, - }); + resetAll(); + const config = { ...dataProvider, params: { key } }; + returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent); }); it('should return true', function () { @@ -75,22 +88,24 @@ describe('Azerion Edge RTD submodule', function () { describe('with process configuration', function () { beforeEach(function () { - window.azerionPublisherAudiences.resetHistory(); - loadExternalScript.resetHistory(); - returned = azerionedgeRTD.azerionedgeSubmodule.init({ - ...dataProvider, - params: { process }, - }); + resetAll(); + const config = { ...dataProvider, params: { process } }; + returned = azerionedgeRTD.azerionedgeSubmodule.init(config, userConsent); }); it('should return true', function () { expect(returned).to.equal(true); }); - it('should call azerionPublisherAudiencesStub with process configuration', function () { - expect(window.azerionPublisherAudiences.args[0][0]).to.deep.equal( - process - ); + [ + ['gdprApplies', userConsent.gdpr.gdprApplies], + ['gdprConsent', userConsent.gdpr.consentString], + ['uspConsent', userConsent.usp], + ...Object.entries(process), + ].forEach(([key, value]) => { + it(`should call azerionPublisherAudiencesStub with ${key}:${value}`, function () { + expect(window.azerionPublisherAudiences.args[0][0]).to.include({[key]: value}); + }); }); }); }); @@ -111,7 +126,7 @@ describe('Azerion Edge RTD submodule', function () { ); }); - it('does not run apply audiences to bidders', function () { + it('does not apply audiences to bidders', function () { expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({}); }); diff --git a/test/spec/modules/beachfrontBidAdapter_spec.js b/test/spec/modules/beachfrontBidAdapter_spec.js index c0994985aae..2e766487951 100644 --- a/test/spec/modules/beachfrontBidAdapter_spec.js +++ b/test/spec/modules/beachfrontBidAdapter_spec.js @@ -245,14 +245,14 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; bidRequest.mediaTypes = { - video: { mimes, playbackmethod, maxduration, placement, skip } + video: { mimes, playbackmethod, maxduration, plcmt, skip } }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must override video params from the bidder object', function () { @@ -260,13 +260,13 @@ describe('BeachfrontAdapter', function () { const mimes = ['video/webm']; const playbackmethod = 2; const maxduration = 30; - const placement = 4; + const plcmt = 4; const skip = 1; - bidRequest.mediaTypes = { video: { placement: 3, skip: 0 } }; - bidRequest.params.video = { mimes, playbackmethod, maxduration, placement, skip }; + bidRequest.mediaTypes = { video: { plcmt: 3, skip: 0 } }; + bidRequest.params.video = { mimes, playbackmethod, maxduration, plcmt, skip }; const requests = spec.buildRequests([ bidRequest ], {}); const data = requests[0].data; - expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, placement, skip }); + expect(data.imp[0].video).to.deep.contain({ mimes, playbackmethod, maxduration, plcmt, skip }); }); it('must add US privacy data to the request', function () { diff --git a/test/spec/modules/bedigitechBidAdapter_spec.js b/test/spec/modules/bedigitechBidAdapter_spec.js index 20d4e86e0c4..336559e2812 100644 --- a/test/spec/modules/bedigitechBidAdapter_spec.js +++ b/test/spec/modules/bedigitechBidAdapter_spec.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; import { spec } from 'modules/bedigitechBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; -import {BANNER} from 'src/mediaTypes.js'; +import { BANNER } from 'src/mediaTypes.js'; describe('BedigitechAdapter', function () { const adapter = newBidder(spec); @@ -34,13 +34,13 @@ describe('BedigitechAdapter', function () { }); it('should return false when required params are not passed', function () { - const bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + const invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'masterId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -126,7 +126,9 @@ describe('BedigitechAdapter', function () { } else if (k === 'meta') { expect(result[0][k]).to.deep.equal(expectedResponse[0][k]); } else { - expect(result[0][k]).to.equal(expectedResponse[0][k]); + if (k !== 'requestId') { + expect(result[0][k]).to.equal(expectedResponse[0][k]); + } } }); }); diff --git a/test/spec/modules/beopBidAdapter_spec.js b/test/spec/modules/beopBidAdapter_spec.js index 663d622e505..0a752761531 100644 --- a/test/spec/modules/beopBidAdapter_spec.js +++ b/test/spec/modules/beopBidAdapter_spec.js @@ -132,7 +132,7 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.url).to.equal('http://test.te'); }); - it('should call the endpoint with psegs and bpsegs (stringified) data if any or [] if none', function () { + it('should call the endpoint with bpsegs (stringified) data if any or [] if none', function () { let bidderRequest = { 'ortb2': { @@ -149,15 +149,14 @@ describe('BeOp Bid Adapter tests', () => { const request = spec.buildRequests(bidRequests, bidderRequest); const payload = JSON.parse(request.data); - expect(payload.psegs).to.exist; - expect(payload.psegs).to.include(1234); - expect(payload.psegs).to.include(5678); - expect(payload.psegs).to.include(910); - expect(payload.psegs).to.not.include(1); expect(payload.bpsegs).to.exist; expect(payload.bpsegs).to.include('axed'); expect(payload.bpsegs).to.include('axec'); expect(payload.bpsegs).to.include('1234'); + expect(payload.bpsegs).to.include('1234'); + expect(payload.bpsegs).to.include('5678'); + expect(payload.bpsegs).to.include('910'); + expect(payload.bpsegs).to.not.include('1'); let bidderRequest2 = { @@ -166,8 +165,6 @@ describe('BeOp Bid Adapter tests', () => { const request2 = spec.buildRequests(bidRequests, bidderRequest2); const payload2 = JSON.parse(request2.data); - expect(payload2.psegs).to.exist; - expect(payload2.psegs).to.be.empty; expect(payload2.bpsegs).to.exist; expect(payload2.bpsegs).to.be.empty; }); @@ -330,4 +327,16 @@ describe('BeOp Bid Adapter tests', () => { expect(payload.eids[0].source).to.equal('provider.com'); }); }) + + describe('Ensure first party cookie is well managed', function () { + let bidRequests = []; + + it(`should generate a new uuid`, function () { + let bid = Object.assign({}, validBid); + bidRequests.push(bid); + const request = spec.buildRequests(bidRequests, {}); + const payload = JSON.parse(request.data); + expect(payload.fg).to.exist; + }) + }) }); diff --git a/test/spec/modules/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 751b3ae1098..9f31294dc54 100644 --- a/test/spec/modules/beyondmediaBidAdapter_spec.js +++ b/test/spec/modules/beyondmediaBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/beyondmediaBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'beyondmedia' +const bidder = 'beyondmedia'; describe('AndBeyondMediaBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('AndBeyondMediaBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('AndBeyondMediaBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -83,8 +107,6 @@ describe('AndBeyondMediaBidAdapter', function () { 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; - expect(spec.isBidRequestValid(bids[1])).to.be.true; - expect(spec.isBidRequestValid(bids[2])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { expect(spec.isBidRequestValid(invalidBid)).to.be.false; @@ -114,6 +136,7 @@ describe('AndBeyondMediaBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -131,7 +154,7 @@ describe('AndBeyondMediaBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -172,8 +195,10 @@ describe('AndBeyondMediaBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +213,38 @@ describe('AndBeyondMediaBidAdapter', function () { 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); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -397,5 +448,17 @@ describe('AndBeyondMediaBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cookies.andbeyond.media/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cookies.andbeyond.media/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/bidResponseFilter_spec.js b/test/spec/modules/bidResponseFilter_spec.js new file mode 100644 index 00000000000..c4b4a776243 --- /dev/null +++ b/test/spec/modules/bidResponseFilter_spec.js @@ -0,0 +1,176 @@ +import { + addBidResponseHook, + BID_ADV_DOMAINS_REJECTION_REASON, + BID_ATTR_REJECTION_REASON, + BID_CATEGORY_REJECTION_REASON, + init, + MODULE_NAME + , reset} from '../../../modules/bidResponseFilter'; +import {config} from '../../../src/config'; +import {addBidResponse} from '../../../src/auction.js'; + +describe('bidResponseFilter', () => { + let mockAuctionIndex + beforeEach(() => { + mockAuctionIndex = { + getBidRequest: () => { + }, + getAdUnit: () => { + } + }; + }); + afterEach(() => { + config.resetConfig(); + reset(); + }) + + describe('enable/disable', () => { + let reject, dispatch; + + beforeEach(() => { + reject = sinon.stub(); + dispatch = sinon.stub(); + }); + + it('should not run if not configured', () => { + reset(); + addBidResponse.call({dispatch}, 'au', {}, reject); + sinon.assert.notCalled(reject); + sinon.assert.called(dispatch); + }); + + it('should run if configured', () => { + config.setConfig({ + bidResponseFilter: {} + }); + addBidResponse.call({dispatch}, 'au', {}, reject); + sinon.assert.called(reject); + sinon.assert.notCalled(dispatch); + }) + }); + + it('should pass the bid after successful ortb2 rules validation', () => { + const call = sinon.stub(); + + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'EXAMPLE-CAT-ID', + attr: 'attr' + } + }; + + addBidResponseHook(call, 'adcode', bid, () => { + }, mockAuctionIndex); + sinon.assert.calledOnce(call); + }); + + it('should reject the bid after failed ortb2 cat rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'attr' + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: [], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_CATEGORY_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 adv domains rule validation', () => { + const rejection = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['domain1.com', 'domain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'attr' + } + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + addBidResponseHook(call, 'adcode', bid, rejection, mockAuctionIndex); + sinon.assert.calledWith(rejection, BID_ADV_DOMAINS_REJECTION_REASON); + }); + + it('should reject the bid after failed ortb2 attr rule validation', () => { + const reject = sinon.stub(); + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'VALID_CAT', + attr: 'BANNED_ATTR' + }, + mediaType: 'video' + }; + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + mockAuctionIndex.getBidRequest = () => ({ + ortb2Imp: { + video: { + battr: 'BANNED_ATTR' + } + } + }) + + addBidResponseHook(call, 'adcode', bid, reject, mockAuctionIndex); + sinon.assert.calledWith(reject, BID_ATTR_REJECTION_REASON); + }); + + it('should omit the validation if the flag is set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: 'BANNED_CAT1', + attr: 'valid_attr' + } + }; + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + config.setConfig({[MODULE_NAME]: {cat: {enforce: false}}}); + + addBidResponseHook(call, 'adcode', bid, () => { + }, mockAuctionIndex); + sinon.assert.calledOnce(call); + }); + + it('should allow bid for unknown flag set to false', () => { + const call = sinon.stub(); + const bid = { + meta: { + advertiserDomains: ['validdomain1.com', 'validdomain2.com'], + primaryCatId: undefined, + attr: 'valid_attr' + } + }; + + mockAuctionIndex.getOrtb2 = () => ({ + badv: ['domain2.com'], bcat: ['BANNED_CAT1', 'BANNED_CAT2'] + }); + + config.setConfig({[MODULE_NAME]: {cat: {blockUnknown: false}}}); + + addBidResponseHook(call, 'adcode', bid, () => { + }); + sinon.assert.calledOnce(call); + }); +}) diff --git a/test/spec/modules/bidViewability_spec.js b/test/spec/modules/bidViewability_spec.js index 1df9aecf73a..5dbf7d84c3c 100644 --- a/test/spec/modules/bidViewability_spec.js +++ b/test/spec/modules/bidViewability_spec.js @@ -245,7 +245,7 @@ describe('#bidViewability', function() { let logWinningBidNotFoundSpy; let callBidViewableBidderSpy; let winningBidsArray; - let callBidBillableBidderSpy; + let triggerBillingSpy; let adUnits = [ { 'code': 'abc123', @@ -262,7 +262,7 @@ describe('#bidViewability', function() { triggerPixelSpy = sandbox.spy(utils, ['triggerPixel']); eventsEmitSpy = sandbox.spy(events, ['emit']); callBidViewableBidderSpy = sandbox.spy(adapterManager, ['callBidViewableBidder']); - callBidBillableBidderSpy = sandbox.spy(adapterManager, ['callBidBillableBidder']); + triggerBillingSpy = sandbox.spy(adapterManager, ['triggerBilling']); // mocking winningBidsArray winningBidsArray = []; sandbox.stub(prebidGlobal, 'getGlobal').returns({ @@ -307,22 +307,16 @@ describe('#bidViewability', function() { expect(eventsEmitSpy.callCount).to.equal(0); }); - it('should call the callBidBillableBidder function if the viewable bid is associated with an ad unit with deferBilling set to true', function() { + it('should call the triggerBilling function if the viewable bid has deferBilling set to true', function() { let moduleConfig = {}; - const deferredBillingAdUnit = { - 'code': '/harshad/Jan/2021/', - 'deferBilling': true, - 'bids': [ - { - 'bidder': 'pubmatic' - } - ] - }; - adUnits.push(deferredBillingAdUnit); - winningBidsArray.push(PBJS_WINNING_BID); + const bid = { + ...PBJS_WINNING_BID, + deferBilling: true + } + winningBidsArray.push(bid); bidViewability.impressionViewableHandler(moduleConfig, GPT_SLOT, null); - expect(callBidBillableBidderSpy.callCount).to.equal(1); - sinon.assert.calledWith(callBidBillableBidderSpy, PBJS_WINNING_BID); + expect(triggerBillingSpy.callCount).to.equal(1); + sinon.assert.calledWith(triggerBillingSpy, bid); }); }); }); diff --git a/test/spec/modules/bidglassAdapter_spec.js b/test/spec/modules/bidglassAdapter_spec.js index 7b007f7cc1f..e0f85364933 100644 --- a/test/spec/modules/bidglassAdapter_spec.js +++ b/test/spec/modules/bidglassAdapter_spec.js @@ -23,10 +23,10 @@ describe('Bid Glass Adapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/bidmaticBidAdapter_spec.js b/test/spec/modules/bidmaticBidAdapter_spec.js new file mode 100644 index 00000000000..4b213c6dcc4 --- /dev/null +++ b/test/spec/modules/bidmaticBidAdapter_spec.js @@ -0,0 +1,269 @@ +import { expect } from 'chai'; +import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js'; +import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils'; + +const expectedImp = { + 'secure': 1, + 'id': '2eb89f0f062afe', + 'banner': { + 'topframe': 0, + 'format': [ + { + 'w': 300, + 'h': 250 + }, + { + 'w': 300, + 'h': 600 + } + ] + }, + 'bidfloor': 0, + 'bidfloorcur': 'USD', + 'tagid': 'div-gpt-ad-1460505748561-0' +} + +describe('Bidmatic Bid Adapter', () => { + const GPID_RTB_EXT = { + 'ortb2Imp': { + 'ext': { + 'gpid': 'gpId', + } + }, + } + const FLOOR_RTB_EXT = { + 'ortb2Imp': { + bidfloor: 1 + }, + } + const DEFAULT_BID_REQUEST = { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'bidder': 'bidmatic', + 'params': { + 'source': 886409, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '7d79850b-70aa-4c0f-af95-c1def0452825', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '2eb89f0f062afe', + 'bidderRequestId': '1ae6c8e18f8462', + 'auctionId': '1286637c-51bc-4fdd-8e35-2435ec11775a', + 'ortb2': {} + }; + + describe('adapter interface', () => { + const bidRequest = deepClone(DEFAULT_BID_REQUEST); + + it('should validate params', () => { + expect(spec.isBidRequestValid({ + params: { + source: 1 + } + })).to.equal(true, 'source param must be a number'); + + expect(spec.isBidRequestValid({ + params: { + source: '1' + } + })).to.equal(false, 'source param must be a number'); + + expect(spec.isBidRequestValid({})).to.equal(false, 'source param must be a number'); + }); + + it('should build hb request', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0]).to.deep.equal(expectedImp); + expect(ortbRequest.data.cur).to.deep.equal(['USD']); + }); + + it('should request with source in url', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + expect(ortbRequest.url).to.equal(`${END_POINT}?source=886409`); + }); + + it('should split http reqs by sources', () => { + const bidRequest2 = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + source: 1111 + } + }); + const [ortbRequest1, ortbRequest2] = spec.buildRequests([bidRequest2, bidRequest, bidRequest2], { + bids: [bidRequest2, bidRequest, bidRequest2] + }); + expect(ortbRequest1.url).to.equal(`${END_POINT}?source=1111`); + expect(ortbRequest1.data.imp.length).to.eq(2) + expect(ortbRequest2.url).to.equal(`${END_POINT}?source=886409`); + expect(ortbRequest2.data.imp.length).to.eq(1) + }); + + it('should grab bid floor info', () => { + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(0) + expect(ortbRequest.data.imp[0].bidfloorcur).eq('USD') + }); + + it('should grab bid floor info from exts', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), FLOOR_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(1) + }); + + it('should grab bid floor info from params', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + params: { + bidfloor: 2 + } + }); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].bidfloor).eq(2) + }); + + it('should set gpid as tagid', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), GPID_RTB_EXT); + const [ortbRequest] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }); + + expect(ortbRequest.data.imp[0].tagid).eq(GPID_RTB_EXT.ortb2Imp.ext.gpid) + }); + }) + + describe('response interpreter', () => { + const SERVER_RESPONSE = { + 'body': { + 'id': '10bb57ee-712f-43e9-9769-b26d03df8a39', + 'seatbid': [ + { + 'bid': [ + { + 'id': 'c5BsBD5QHHgx4aS8', + 'impid': '2eb89f0f062afe', + 'price': 1, + 'adid': 'BDhclfXLcGzRMeV', + 'adm': '123', + 'adomain': [ + 'https://test.com' + ], + 'crid': 'display_300x250', + 'w': 300, + 'h': 250, + } + ], + 'seat': '1' + } + ], + 'cur': 'USD' + }, + 'headers': {} + } + + it('should return empty results', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(null, { + data: req.data + }) + + expect(result.length).to.eq(0); + }); + it('should detect media type based on adm', () => { + const [req] = spec.buildRequests([deepClone(DEFAULT_BID_REQUEST)], { + bids: [deepClone(DEFAULT_BID_REQUEST)] + }) + const result = spec.interpretResponse(SERVER_RESPONSE, { + data: req.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + it('should detect video adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + deepSetValue(bannerResponse, 'body.seatbid.0.bid.0.adm', ''); + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('video') + }); + + it('should detect banner adm', () => { + const bidRequest = mergeDeep(deepClone(DEFAULT_BID_REQUEST), { + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [640, 480] + } + } + }) + const bannerResponse = deepClone(SERVER_RESPONSE); + const [ortbReq] = spec.buildRequests([bidRequest], { + bids: [bidRequest] + }) + const result = spec.interpretResponse(bannerResponse, { + data: ortbReq.data + }) + + expect(result.length).to.eq(1); + expect(result[0].mediaType).to.eq('banner') + }); + }) +}) diff --git a/test/spec/modules/bidtheatreBidAdapter_spec.js b/test/spec/modules/bidtheatreBidAdapter_spec.js new file mode 100644 index 00000000000..4842c43d1f0 --- /dev/null +++ b/test/spec/modules/bidtheatreBidAdapter_spec.js @@ -0,0 +1,266 @@ +import { expect } from 'chai'; +import { spec, ENDPOINT_URL, BIDDER_CODE, DEFAULT_CURRENCY } from 'modules/bidtheatreBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; +import { deepClone } from 'src/utils.js'; + +const VALID_PUBLISHER_ID = '73b20b3a-12a0-4869-b54e-8d42b55786ee'; +const STATIC_IMP_ID = '3263e5dec855c5'; +const BID_PRICE = 5.112871170043945; +const AUCTION_PRICE_MACRO = '${AUCTION_PRICE}'; + +const BANNER_BID_REQUEST = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'publisherId': VALID_PUBLISHER_ID + }, + 'bidId': STATIC_IMP_ID, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 980, + 240 + ] + ] + } + }, + 'sizes': [ + [ + 980, + 240 + ] + ] + } +]; + +const BANNER_BIDDER_REQUEST = {'bidderCode': BIDDER_CODE, 'bids': BANNER_BID_REQUEST}; + +const BANNER_BID_RESPONSE = { + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '5', + 'bid': [ + { + 'ext': { + 'usersync_urls': [ + 'https://match.adsby.bidtheatre.com/usersync?gdpr=1&gdpr_consent=CONSENT_STRING' + ] + }, + 'crid': '1915538', + 'h': 240, + 'adm': "", + 'mtype': 1, + 'adid': '1915538', + 'adomain': [ + 'bidtheatre.com' + ], + 'price': BID_PRICE, + 'cat': [ + 'IAB3-1' + ], + 'w': 980, + 'id': STATIC_IMP_ID, + 'impid': STATIC_IMP_ID, + 'cid': 'c154375' + } + ] + } + ] +}; + +const VIDEO_BID_REQUEST = [ + { + 'bidder': BIDDER_CODE, + 'params': { + 'publisherId': VALID_PUBLISHER_ID + }, + 'bidId': STATIC_IMP_ID, + 'mediaTypes': { + 'video': { + 'playerSize': [ + [ + 1280, + 720 + ] + ], + 'context': 'instream' + } + }, + 'sizes': [[1280, 720]] + } +]; + +const VIDEO_BIDDER_REQUEST = {'bidderCode': BIDDER_CODE, 'bids': VIDEO_BID_REQUEST}; + +const VIDEO_BID_RESPONSE = { + 'cur': 'USD', + 'seatbid': [ + { + 'seat': '5', + 'bid': [ + { + 'ext': { + 'usersync_urls': [ + 'https://match.adsby.bidtheatre.com/usersync?gdpr=0&gdpr_consent=' + ] + }, + 'crid': '1922926', + 'h': 720, + 'mtype': 2, + 'nurl': 'https://adsby.bidtheatre.com/video?z=27025;a=1922926;ex=36;es=http%3A%2F%2F127.0.0.1%3A8080;eb=3672319;xs=940400838;so=1;tag=unspec_1280_720;kuid=05914b22-88cb-4c5d-9f7c-f133fdf9669a;wp=${AUCTION_PRICE};su=127.0.0.1%3A8080;iab=vast2;dealId=;ma=eyJjZCI6ZmFsc2UsInN0IjozLCJtbGF0Ijo1OS4yNywibW9yZyI6InRlbGlhIG5ldHdvcmsgc2VydmljZXMiLCJtbHNjb3JlIjowLjg2MDcwMDU0NzY5NTE1OTksIm16aXAiOiIxMjggMzUiLCJiaXAiOiI4MS4yMjcuODIuMjgiLCJhZ2lkIjozNTYyNzAyLCJtbG1vZGVsIjoibWFzdGVyX21sX2Nsa181MzYiLCJ1YSI6Ik1vemlsbGFcLzUuMCAoTWFjaW50b3NoOyBJbnRlbCBNYWMgT1MgWCAxMF8xNV83KSBBcHBsZVdlYktpdFwvNTM3LjM2IChLSFRNTCwgbGlrZSBHZWNrbykgQ2hyb21lXC8xMzAuMC4wLjAgU2FmYXJpXC81MzcuMzYiLCJtbG9uIjoxOC4xMywibXJlZ2lvbiI6ImFiIiwiZHQiOjEsIm1jaXR5Ijoic2thcnBuYWNrIiwicGFnZXVybCI6Imh0dHA6XC9cLzEyNy4wLjAuMTo4MDgwXC92aWRlby5odG1sP3BianNfZGVidWc9dHJ1ZSIsImltcGlkIjoieDM2X2FzeC1iLXMyXzQxNDMzMTA0MTIyMzUyNTU3NDgiLCJtY291bnRyeSI6InN3ZSIsInRzIjoxNzMxNTA3NTY5Njg3fQ%3D%3D;cd=0;cb0=;impId=x36_asx-b-s2_4143310412235255748;gdpr=1;gdpr_consent=CP-S4UAP-S4UACGABBENAzEsAP_gAEPgAAAAKKtV_H__bW1r8X73aft0eY1P9_j77sQxBhfJE-4FzLvW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2D-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v0_F_rE2_eT1l_tevp7D9-ct87_XW-9_fff79Ll9-goqAWYaFRAHWBISEGgYRQIAVBWEBFAgAAABIGiAgBMGBTsDAJdYSIAQAoABggBAACjIAEAAAEACEQAQAFAgAAgECgABAAgEAgAIGAAEAFgIBAACA6BCmBBAoFgAkZkRCmBCFAkEBLZUIJAECCuEIRZ4AEAiJgoAAAAACsAAQFgsDiSQEqEggS4g2gAAIAEAghAqEEnJgACBI2WoPBE2jK0gDQ04SAAAAA.YAAAAAAAAAAA', + 'adid': '1922926', + 'adomain': [ + 'bidtheatre.com' + ], + 'price': BID_PRICE, + 'cat': [ + 'IAB3-1' + ], + 'w': 1280, + 'id': STATIC_IMP_ID, + 'impid': STATIC_IMP_ID, + 'cid': 'c154375' + } + ] + } + ] +} + +describe('BidtheatreAdapter', function () { + describe('isBidRequestValid', function () { + let bid = { + 'bidder': BIDDER_CODE, + 'params': { + 'publisherId': VALID_PUBLISHER_ID + }, + 'sizes': [[980, 240]] + }; + + it('should return true when required param found and of correct type', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required param is not passed', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { + + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when required param of incorrect data type', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { + 'publisherId': 12345 + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when required param of incorrect length', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { + 'publisherId': '73b20b3a-12a0-4869-b54e-8d42b55786e' + }; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + it('should include correct http method, correct url and existing data', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + expect(request[0].method).to.equal('POST'); + expect(request[0].url).to.equal(ENDPOINT_URL); + expect(request[0].data).to.exist; + }); + + it('should include required bid param in request', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + const data = request[0].data; + expect(data.imp[0].ext.bidder.publisherId).to.equal(VALID_PUBLISHER_ID); + }); + + it('should include imp array in request', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + const data = request[0].data; + expect(data).to.have.property('imp').that.is.an('array').with.lengthOf.at.least(1); + expect(data.imp[0]).to.be.an('object'); + }); + }); + + describe('interpretResponse', function () { + it('should have exactly one bid in banner response', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]); + expect(bids).to.be.an('array').with.lengthOf(1); + expect(bids[0]).to.be.an('object'); + }); + + it('should have currency set to USD in banner response', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]); + expect(bids[0].currency).to.be.a('string').and.to.equal(DEFAULT_CURRENCY); + }); + + it('should have ad in response and auction price macros replaced in banner response', function () { + const request = spec.buildRequests(BANNER_BID_REQUEST, BANNER_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: BANNER_BID_RESPONSE}, request[0]); + const ad = bids[0].ad; + expect(ad).to.exist; + expect(ad).to.be.a('string'); + expect(ad).to.include('&wp=' + BID_PRICE + '&'); + expect(ad).to.not.include(AUCTION_PRICE_MACRO); + }); + + if (FEATURES.VIDEO) { + it('should have exactly one bid in video response', function () { + const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]); + expect(bids).to.be.an('array').with.lengthOf(1); + expect(bids[0]).to.be.an('object'); + }); + + it('should have currency set to USD in video response', function () { + const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]); + expect(bids[0].currency).to.be.a('string').and.to.equal(DEFAULT_CURRENCY); + }); + + it('should have vastUrl in response and auction price macros replaced in video response', function () { + const request = spec.buildRequests(VIDEO_BID_REQUEST, VIDEO_BIDDER_REQUEST); + const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request[0]); + const vastUrl = bids[0].vastUrl; + expect(vastUrl).to.exist; + expect(vastUrl).to.be.a('string'); + expect(vastUrl).to.include(';wp=' + BID_PRICE + ';'); + expect(vastUrl).to.not.include(AUCTION_PRICE_MACRO); + }); + } + }); + + describe('getUserSyncs', function () { + const bidResponse = deepClone(BANNER_BID_RESPONSE); + const bidResponseSyncURL = bidResponse.seatbid[0].bid[0].ext.usersync_urls[0]; + + const gdprConsent = { + gdprApplies: true, + consentString: 'CONSENT_STRING' + }; + + it('should return empty when pixel is disallowed', function () { + expect(spec.getUserSyncs({ pixelEnabled: false }, bidResponse, gdprConsent)).to.be.empty; + }); + + it('should return empty when no server response is present', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, [], gdprConsent)).to.be.empty; + }); + + it('should return usersync url when pixel is allowed and present in bid response', function () { + expect(spec.getUserSyncs({ pixelEnabled: true }, [{body: bidResponse}], gdprConsent)).to.deep.equal([{ type: 'image', url: bidResponseSyncURL }]); + }); + }); +}); diff --git a/test/spec/modules/bizzclickBidAdapter_spec.js b/test/spec/modules/blastoBidAdapter_spec.js similarity index 97% rename from test/spec/modules/bizzclickBidAdapter_spec.js rename to test/spec/modules/blastoBidAdapter_spec.js index f8e66caf657..671f99fa938 100644 --- a/test/spec/modules/bizzclickBidAdapter_spec.js +++ b/test/spec/modules/blastoBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from 'modules/bizzclickBidAdapter'; +import { spec } from 'modules/blastoBidAdapter'; import 'modules/priceFloors.js'; import { newBidder } from 'src/adapters/bidderFactory'; import { config } from '../../../src/config.js'; @@ -11,12 +11,12 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; const SIMPLE_BID_REQUEST = { - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -46,7 +46,7 @@ const SIMPLE_BID_REQUEST = { } const BANNER_BID_REQUEST = { - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -85,7 +85,7 @@ const VIDEO_BID_REQUEST = { protocols: [1, 2, 4] } }, - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: '123', sourceId: '123', @@ -128,7 +128,7 @@ const NATIVE_BID_REQUEST = { } } }, - bidder: 'bizzclick', + bidder: 'blasto', params: { accountId: 'testAccountId', sourceId: 'testSourceId', @@ -158,7 +158,7 @@ const gdprConsent = { addtlConsent: '1~1.35.41.101', } -describe('bizzclickAdapter', function () { +describe('blastoAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { it('exists and is a function', function () { @@ -251,7 +251,7 @@ describe('bizzclickAdapter', function () { beforeEach(function () { bidRequests = [{ 'bidId': '28ffdk2B952532', - 'bidder': 'bizzclick', + 'bidder': 'blasto', 'userId': { 'freepassId': { 'userIp': '172.21.0.1', diff --git a/test/spec/modules/bliinkBidAdapter_spec.js b/test/spec/modules/bliinkBidAdapter_spec.js index 3db97a17d88..ff48d8579a7 100644 --- a/test/spec/modules/bliinkBidAdapter_spec.js +++ b/test/spec/modules/bliinkBidAdapter_spec.js @@ -7,9 +7,14 @@ import { BLIINK_ENDPOINT_COOKIE_SYNC_IFRAME, getEffectiveConnectionType, getUserIds, - getDomLoadingDuration, GVL_ID, } from 'modules/bliinkBidAdapter.js'; +import { + canAccessWindowTop, + getDomLoadingDuration, + getWindowSelf, + getWindowTop +} from 'src/utils.js'; import { config } from 'src/config.js'; /** @@ -32,8 +37,9 @@ import { config } from 'src/config.js'; * ortb2Imp: {ext: {data: {pbadslot: string}}}}} */ +const w = (canAccessWindowTop()) ? getWindowTop() : getWindowSelf(); const connectionType = getEffectiveConnectionType(); -const domLoadingDuration = getDomLoadingDuration().toString(); +const domLoadingDuration = getDomLoadingDuration(w).toString(); const getConfigBid = (placement) => { return { adUnitCode: '/19968336/test', diff --git a/test/spec/modules/bluebillywigBidAdapter_spec.js b/test/spec/modules/bluebillywigBidAdapter_spec.js deleted file mode 100644 index b6fb11c4750..00000000000 --- a/test/spec/modules/bluebillywigBidAdapter_spec.js +++ /dev/null @@ -1,1094 +0,0 @@ -import {expect} from 'chai'; -import {spec} from 'modules/bluebillywigBidAdapter.js'; -import {deepAccess, deepClone} from 'src/utils.js'; -import {config} from 'src/config.js'; -import {VIDEO} from 'src/mediaTypes.js'; - -const BB_CONSTANTS = { - BIDDER_CODE: 'bluebillywig', - AUCTION_URL: '$$URL_STARTpbs.bluebillywig.com/openrtb2/auction?pub=$$PUBLICATION', - SYNC_URL: '$$URL_STARTpbs.bluebillywig.com/static/cookie-sync.html?pub=$$PUBLICATION', - RENDERER_URL: 'https://$$PUBLICATION.bbvms.com/r/$$RENDERER.js', - DEFAULT_TIMEOUT: 5000, - DEFAULT_TTL: 300, - DEFAULT_WIDTH: 768, - DEFAULT_HEIGHT: 432, - DEFAULT_NET_REVENUE: true -}; - -describe('BlueBillywigAdapter', () => { - describe('isBidRequestValid', () => { - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: 'bbprebid.dev', - rendererCode: 'glorious_renderer', - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - it('should return true when required params found', () => { - 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; - - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when publicationName is not a string', () => { - const bid = deepClone(baseValidBid); - - bid.params.publicationName = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when publicationName is formatted poorly', () => { - const bid = deepClone(baseValidBid); - - bid.params.publicationName = 'bb.'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = 'bb-test'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.publicationName = '?'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.rendererCode; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is not a string', () => { - const bid = deepClone(baseValidBid); - - bid.params.rendererCode = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when renderer is formatted poorly', () => { - const bid = deepClone(baseValidBid); - - bid.params.rendererCode = 'bb.'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = 'bb-test'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.rendererCode = '?'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when accountId is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.accountId; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when connections is not specified', () => { - const bid = deepClone(baseValidBid); - - delete bid.params.connections; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when connections is not an array', () => { - const bid = deepClone(baseValidBid); - - bid.params.connections = 123; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = false; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = void (0); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections = 'string'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when a connection is missing', () => { - const bid = deepClone(baseValidBid); - - bid.params.connections.push('potatoes'); - expect(spec.isBidRequestValid(bid)).to.equal(false); - - bid.params.connections.pop(); - - delete bid.params[BB_CONSTANTS.BIDDER_CODE]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes.video', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes[VIDEO]; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if bid has no mediaTypes.video.context', () => { - const bid = deepClone(baseValidBid); - - delete bid.mediaTypes[VIDEO].context; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail if mediaTypes.video.context is not "outstream"', () => { - const bid = deepClone(baseValidBid); - - 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', () => { - const publicationName = 'bbprebid.dev'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const baseValidBidRequests = [baseValidBid]; - - const validBidderRequest = { - ortb2: { - source: { - tid: '12abc345-67d8-9012-e345-6f78901a2b34', - } - }, - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - - it('sends bid request to AUCTION_URL via POST', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request.url).to.equal(`https://pbs.bluebillywig.com/openrtb2/auction?pub=${publicationName}`); - expect(request.method).to.equal('POST'); - }); - - it('sends data as a string', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request.data).to.be.a('string'); - }); - - it('sends all bid parameters', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - expect(request).to.have.all.keys(['bidderRequest', 'data', 'method', 'url']); - }); - - it('builds the base request properly', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.id).to.exist; - expect(payload.source).to.be.an('object'); - expect(payload.source.tid).to.equal(validBidderRequest.ortb2.source.tid); - expect(payload.tmax).to.equal(3000); - expect(payload.imp).to.be.an('array'); - expect(payload.test).to.be.a('number'); - expect(payload).to.have.nested.property('ext.prebid.targeting'); - expect(payload.ext.prebid.targeting).to.be.an('object'); - expect(payload.ext.prebid.targeting.includewinners).to.equal(true); - expect(payload.ext.prebid.targeting.includebidderkeys).to.equal(false); - }); - - it('adds an impression to the payload', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.imp.length).to.equal(1); - }); - - it('adds connections to ext', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.imp[0].ext).to.have.all.keys(['bluebillywig']); - }); - - it('adds gdpr when present', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.gdprConsent = { - consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAAAAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - gdprApplies: true - }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.gdpr'); - expect(payload.regs.ext.gdpr).to.be.a('number'); - expect(payload.regs.ext.gdpr).to.equal(1); - expect(payload).to.have.nested.property('user.ext.consent'); - expect(payload.user.ext.consent).to.equal(newValidBidderRequest.gdprConsent.consentString); - }); - - it('sets gdpr to 0 when explicitly gdprApplies: false', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.gdprConsent = { - gdprApplies: false - }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.gdpr'); - expect(payload.regs.ext.gdpr).to.be.a('number'); - expect(payload.regs.ext.gdpr).to.equal(0); - }); - - it('adds usp_consent when present', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.uspConsent = '1YYY'; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.ext.us_privacy'); - expect(payload.regs.ext.us_privacy).to.equal(newValidBidderRequest.uspConsent); - }); - - it('sets coppa to 1 when specified in config', () => { - config.setConfig({'coppa': true}); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('regs.coppa'); - expect(payload.regs.coppa).to.equal(1); - - config.resetConfig(); - }); - - it('does not set coppa when disabled in the config', () => { - config.setConfig({'coppa': false}); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; - - config.resetConfig(); - }); - - it('does not set coppa when not specified in config', () => { - config.resetConfig(); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'regs.coppa')).to.be.undefined; - }); - - it('should add window size to request by default', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('device.w'); - expect(payload).to.have.nested.property('device.h'); - expect(payload.device.w).to.be.a('number'); - expect(payload.device.h).to.be.a('number'); - }); - - it('should add site when specified in config', () => { - config.setConfig({ site: { name: 'Blue Billywig', domain: 'bluebillywig.com', page: 'https://bluebillywig.com/', publisher: { id: 'abc', name: 'Blue Billywig', domain: 'bluebillywig.com' } } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('site'); - expect(payload).to.have.nested.property('site.name'); - expect(payload).to.have.nested.property('site.domain'); - expect(payload).to.have.nested.property('site.page'); - expect(payload).to.have.nested.property('site.publisher'); - expect(payload).to.have.nested.property('site.publisher.id'); - expect(payload).to.have.nested.property('site.publisher.name'); - expect(payload).to.have.nested.property('site.publisher.domain'); - - config.resetConfig(); - }); - - it('should add app when specified in config', () => { - config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('app'); - expect(payload).to.have.nested.property('app.bundle'); - expect(payload).to.have.nested.property('app.domain'); - expect(payload.app.bundle).to.equal('org.prebid.mobile.demoapp'); - expect(payload.app.domain).to.equal('prebid.org'); - - config.resetConfig(); - }); - - it('should add referrerInfo as site when no app is set', () => { - const newValidBidderRequest = deepClone(validBidderRequest); - - newValidBidderRequest.refererInfo = { page: 'https://www.bluebillywig.com' }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('site.page'); - expect(payload.site.page).to.equal('https://www.bluebillywig.com'); - }); - - it('should not add referrerInfo as site when app is set', () => { - config.setConfig({ app: { bundle: 'org.prebid.mobile.demoapp', domain: 'prebid.org' } }); - - const newValidBidderRequest = deepClone(validBidderRequest); - newValidBidderRequest.refererInfo = { referer: 'https://www.bluebillywig.com' }; - - const request = spec.buildRequests(baseValidBidRequests, newValidBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.site).to.be.undefined; - config.resetConfig(); - }); - - it('should add device size to request when specified in config', () => { - config.setConfig({ device: { w: 1, h: 1 } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('device.w'); - expect(payload).to.have.nested.property('device.h'); - expect(payload.device.w).to.be.a('number'); - expect(payload.device.h).to.be.a('number'); - expect(payload.device.w).to.equal(1); - expect(payload.device.h).to.equal(1); - - config.resetConfig(); - }); - - it('should set schain on the request when set on config', () => { - const schain = { - validation: 'lax', - config: { - ver: '1.0', - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - }; - - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].schain = schain; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('source.ext.schain'); - expect(payload.source.ext.schain).to.deep.equal(schain); - }); - - it('should add currency when specified on the config', () => { - config.setConfig({ currency: { adServerCurrency: 'USD' } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('cur'); - expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally - - config.resetConfig(); - }); - - it('should also take in array for currency on the config', () => { - config.setConfig({ currency: { adServerCurrency: ['USD', 'PHP'] } }); - - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.property('cur'); - expect(payload.cur).to.eql(['USD']); // NB not equal, eql to check for same array because [1] === [1] fails normally - - config.resetConfig(); - }); - - it('should not set cur when currency is not specified on the config', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload.cur).to.be.undefined; - }); - - it('should set user ids when present', () => { - const newBaseValidBidRequests = deepClone(baseValidBidRequests); - newBaseValidBidRequests[0].userIdAsEids = [ {} ]; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(payload).to.have.nested.property('user.ext.eids'); - expect(payload.user.ext.eids).to.be.an('array'); - expect(payload.user.ext.eids.length).to.equal(1); - }); - - it('should not set user ids when none present', () => { - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'user.ext.eids')).to.be.undefined; - }); - - 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].mediaTypes.video.playerSize = [1, 1]; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - expect(deepAccess(payload, 'imp.0.video.w')).to.equal(1); - expect(deepAccess(payload, 'imp.0.video.h')).to.equal(1); - }); - - 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].params.video = { - 'true': true, - 'testing': 'some', - 123: {}, - '': 'values' - }; - - const request = spec.buildRequests(newBaseValidBidRequests, validBidderRequest); - const payload = JSON.parse(request.data); - - 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', () => { - const publicationName = 'bbprebid.dev'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const baseValidBidRequests = [baseValidBid]; - - const validBidderRequest = { - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - - const validResponse = { - id: 'a12abc345-67d8-9012-e345-6f78901a2b34', - seatbid: [ - { - bid: [ - { - id: '1', - impid: '1234ab567c89de0', - price: 1, - adm: '\r\nBB Adserver00:00:51', - adid: '67069817', - adomain: [ - 'bluebillywig.com' - ], - cid: '3535', - crid: '67069817', - w: 1, - h: 1, - publicationName: 'bbprebid', - accountId: 123, - ext: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '1.00', - hb_size: '1x1' - }, - type: 'video' - }, - bidder: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '10.00', - hb_size: '1x1' - }, - type: 'video', - video: { - duration: 51, - primary_category: '' - } - }, - bidder: { - bluebillywig: { - brand_id: 1, - auction_id: 1, - bid_ad_type: 1, - creative_info: { - video: { - duration: 51, - mimes: [ - 'video/x-flv', - 'video/mp4', - 'video/webm' - ] - } - } - } - } - } - } - } - ], - seat: 'bluebillywig' - } - ], - cur: 'USD', - ext: { - responsetimemillis: { - bluebillywig: 0 - }, - tmaxrequest: 5000 - } - }; - - const serverResponse = { body: validResponse }; - - it('should build bid array', () => { - const response = deepClone(serverResponse); - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(1); - }); - - it('should have all relevant fields', () => { - const response = deepClone(serverResponse); - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - const bid = result[0]; - - // BB_HELPERS.transformRTBToPrebidProps - expect(bid.cpm).to.equal(serverResponse.body.seatbid[0].bid[0].price); - expect(bid.bidId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); - expect(bid.requestId).to.equal(serverResponse.body.seatbid[0].bid[0].impid); - expect(bid.width).to.equal(serverResponse.body.seatbid[0].bid[0].w || BB_CONSTANTS.DEFAULT_WIDTH); - expect(bid.height).to.equal(serverResponse.body.seatbid[0].bid[0].h || BB_CONSTANTS.DEFAULT_HEIGHT); - expect(bid.ad).to.equal(serverResponse.body.seatbid[0].bid[0].adm); - expect(bid.netRevenue).to.equal(BB_CONSTANTS.DEFAULT_NET_REVENUE); - expect(bid.creativeId).to.equal(serverResponse.body.seatbid[0].bid[0].crid); - 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); - }); - - it('should not give anything when seatbid is an empty array', () => { - const seatbidEmptyArray = deepClone(serverResponse); - seatbidEmptyArray.body.seatbid = []; - - const response = seatbidEmptyArray; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid is missing', () => { - const seatbidMissing = deepClone(serverResponse); - delete seatbidMissing.body.seatbid; - - const response = seatbidMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - const seatbidNotArrayResponse = deepClone(serverResponse); - it('should not give anything when seatbid is not an array', () => { - const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; - - for (const invalidValue of invalidValues) { - seatbidNotArrayResponse.body.seatbid = invalidValue - const response = deepClone(seatbidNotArrayResponse); // interpretResponse is destructive - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - } - }); - - it('should not give anything when seatbid.bid is an empty array', () => { - const seatbidBidEmpty = deepClone(serverResponse); - seatbidBidEmpty.body.seatbid[0].bid = []; - - const response = seatbidBidEmpty; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid.bid is missing', () => { - const seatbidBidMissing = deepClone(serverResponse); - delete seatbidBidMissing.body.seatbid[0].bid; - - const response = seatbidBidMissing; - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - expect(result.length).to.equal(0); - }); - - it('should not give anything when seatbid.bid is not an array', () => { - const seatbidBidNotArray = deepClone(serverResponse); - - const invalidValues = [ false, null, {}, void (0), 123, 'string' ]; - - for (const invalidValue of invalidValues) { - seatbidBidNotArray.body.seatbid[0].bid = invalidValue; - - const response = deepClone(seatbidBidNotArray); // interpretResponse is destructive - const request = spec.buildRequests(baseValidBidRequests, validBidderRequest); - const result = spec.interpretResponse(response, request); - - 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'; - const rendererCode = 'glorious_renderer'; - - const baseValidBid = { - bidder: BB_CONSTANTS.BIDDER_CODE, - params: { - accountId: 123, - publicationName: publicationName, - rendererCode: rendererCode, - connections: [ BB_CONSTANTS.BIDDER_CODE ], - bluebillywig: {} - }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }; - - const validBidRequests = [baseValidBid]; - - const validBidderRequest = { - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - auctionStart: 1585918458868, - bidderCode: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - bids: [{ - adUnitCode: 'ad-unit-test', - auctionId: '12abc345-67d8-9012-e345-6f78901a2b34', - bidId: '1234ab567c89de0', - bidRequestsCount: 1, - bidder: BB_CONSTANTS.BIDDER_CODE, - bidderRequestId: '1a2345b67c8d9e0', - params: baseValidBid.params, - sizes: [[768, 432], [640, 480], [630, 360]], - transactionId: '2b34c5de-f67a-8901-bcd2-34567efabc89' - }], - start: 11585918458869, - timeout: 3000 - }; - const validResponse = { - id: 'a12abc345-67d8-9012-e345-6f78901a2b34', - seatbid: [ - { - bid: [ - { - id: '1', - impid: '1234ab567c89de0', - price: 1, - adm: '\r\nBB Adserver00:00:51', - adid: '67069817', - adomain: [ - 'bluebillywig.com' - ], - cid: '3535', - crid: '67069817', - w: 1, - h: 1, - publicationName: 'bbprebid', - accountId: 123, - ext: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '1.00', - hb_size: '1x1' - }, - type: 'video' - }, - bidder: { - prebid: { - targeting: { - hb_bidder: 'bluebillywig', - hb_pb: '10.00', - hb_size: '1x1' - }, - type: 'video', - video: { - duration: 51, - primary_category: '' - } - }, - bidder: { - bluebillywig: { - brand_id: 1, - auction_id: 1, - bid_ad_type: 1, - creative_info: { - video: { - duration: 51, - mimes: [ - 'video/x-flv', - 'video/mp4', - 'video/webm' - ] - } - } - } - } - } - } - } - ], - seat: 'bluebillywig' - } - ], - cur: 'USD', - ext: { - responsetimemillis: { - bluebillywig: 0 - }, - tmaxrequest: 5000 - } - }; - - const serverResponse = { body: validResponse }; - - const gdpr = { - consentString: 'BOh7mtYOh7mtYAcABBENCU-AAAAncgPIXJiiAoao0PxBFkgCAC8ACIAAQAQQAAIAAAIAAAhBGAAAQAQAEQgAAAAAAABAAAAAAAAA AAAAAACAAAAAAAACgAAAAABAAAAQAAAAAAA', - gdprApplies: true - }; - - it('should return empty if no server response', function () { - const result = spec.getUserSyncs({}, false, gdpr); - expect(result).to.be.empty; - }); - - it('should return empty if server response is empty', function () { - const result = spec.getUserSyncs({}, [], gdpr); - expect(result).to.be.empty; - }); - - it('should return empty if iframeEnabled is not true', () => { - const result = spec.getUserSyncs({iframeEnabled: false}, [serverResponse], gdpr); - expect(result).to.be.empty; - }); - - it('should append the various values if they exist', function() { - // push data to syncStore - spec.buildRequests(validBidRequests, validBidderRequest); - - const result = spec.getUserSyncs({iframeEnabled: true}, [serverResponse], gdpr); - - expect(result).to.not.be.empty; - - expect(result[0].url).to.include('gdpr=1'); - expect(result[0].url).to.include(gdpr.consentString); - expect(result[0].url).to.include('accountId=123'); - expect(result[0].url).to.include(`bidders=${btoa(JSON.stringify(validBidRequests[0].params.connections))}`); - expect(result[0].url).to.include('cb='); - }); - }); -}); diff --git a/test/spec/modules/boldwinBidAdapter_spec.js b/test/spec/modules/boldwinBidAdapter_spec.js index 9a7b16c0914..820938dcec2 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -1,115 +1,221 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/boldwinBidAdapter.js'; -import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/boldwinBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'boldwin'; describe('BoldwinBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'boldwin', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - banner: { - sizes: [ [300, 250], [320, 50] ], + [BANNER]: { + sizes: [[300, 250]] } }, params: { - placementId: 'testBanner', + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - ortb2: {} + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { - it('Should return true if there are bidId, params and placementId parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + it('Should return true if there are bidId, params and key parameters present', function () { + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://ssp.videowalldirect.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'sizes', 'hPlayer', 'wPlayer', 'schain', 'bidFloor', 'type'); - expect(placement.placementId).to.equal('testBanner'); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.adFormat).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.type).to.exist.and.to.equal('publisher'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.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 valid placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); 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); + 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([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { @@ -119,7 +225,7 @@ describe('BoldwinBidAdapter', function () { applicableSections: [8] }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); @@ -129,15 +235,18 @@ describe('BoldwinBidAdapter', function () { }) it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; bidderRequest.ortb2.regs.gpp = 'abc123'; bidderRequest.ortb2.regs.gpp_sid = [8]; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; }) }); @@ -155,7 +264,11 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -163,18 +276,17 @@ describe('BoldwinBidAdapter', function () { 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('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.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); - it('Should interpret video response', function () { const video = { body: [{ @@ -186,7 +298,11 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -220,6 +336,10 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -307,14 +427,41 @@ describe('BoldwinBidAdapter', function () { }); }); - 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://sync.videowalldirect.com'); + 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://sync.videowalldirect.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://sync.videowalldirect.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://sync.videowalldirect.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/bridBidAdapter_spec.js b/test/spec/modules/bridBidAdapter_spec.js index 7503c748999..fdda6d840e8 100644 --- a/test/spec/modules/bridBidAdapter_spec.js +++ b/test/spec/modules/bridBidAdapter_spec.js @@ -1,4 +1,5 @@ import { spec } from '../../../modules/bridBidAdapter.js' +import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js'; describe('Brid Bid Adapter', function() { const videoRequest = [{ @@ -126,4 +127,23 @@ describe('Brid Bid Adapter', function() { expect(payload.regs.ext.gdpr).to.be.undefined; expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); }); + + it('Test userSync have only one object and it should have a property type=iframe', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true }); + expect(userSync).to.be.an('array'); + expect(userSync.length).to.be.equal(1); + expect(userSync[0]).to.have.property('type'); + expect(userSync[0].type).to.be.equal('iframe'); + }); + + it('Test userSync valid sync url for iframe', function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, {}, {consentString: 'anyString'}); + expect(userSync.url).to.contain(SYNC_URL + 'load-cookie.html?endpoint=brid&gdpr=0&gdpr_consent=anyString'); + expect(userSync.type).to.be.equal('iframe'); + }); + + it('Test userSyncs iframeEnabled=false', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false}); + expect(userSyncs).to.have.lengthOf(0); + }); }); diff --git a/test/spec/modules/brightcomBidAdapter_spec.js b/test/spec/modules/brightcomBidAdapter_spec.js deleted file mode 100644 index 1ae73708d00..00000000000 --- a/test/spec/modules/brightcomBidAdapter_spec.js +++ /dev/null @@ -1,411 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec } from 'modules/brightcomBidAdapter.js'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import {config} from '../../../src/config'; - -const URL = 'https://brightcombid.marphezis.com/hb'; - -describe('brightcomBidAdapter', 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': 'brightcom', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - } - ] - }, - }]; - - 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': 'brightcom', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - '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 publisherId 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].mediaTypes.banner.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); - }); - - 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: { - page: 'http://example.com/page.html', - domain: 'example.com', - } - }; - 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); - }); - - it('sends us_privacy', function () { - const bidderRequest = { - uspConsent: '1YYY' - }; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) - - expect(data.regs).to.not.equal(null); - expect(data.regs.ext).to.not.equal(null); - expect(data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('sends coppa', function () { - sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); - - const data = JSON.parse(spec.buildRequests(bidRequests).data) - expect(data.regs).to.not.be.undefined; - expect(data.regs.coppa).to.equal(1); - }); - - it('sends schain', function () { - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data).to.not.be.undefined; - expect(data.source).to.not.be.undefined; - expect(data.source.ext).to.not.be.undefined; - expect(data.source.ext.schain).to.not.be.undefined; - expect(data.source.ext.schain.complete).to.equal(1); - expect(data.source.ext.schain.ver).to.equal('1.0'); - expect(data.source.ext.schain.nodes).to.not.be.undefined; - expect(data.source.ext.schain.nodes).to.lengthOf(1); - expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); - expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); - expect(data.source.ext.schain.nodes[0].hp).to.equal(1); - expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); - expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); - expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); - }); - - it('sends user eid parameters', function () { - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' - } - }] - } - ]; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.eids).to.not.be.undefined; - expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); - }); - - it('sends user id parameters', function () { - const userId = { - sharedid: { - id: '01*******', - third: '01E*******' - } - }; - - bidRequests[0].userId = userId; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.ids).is.deep.equal(userId); - }); - - 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].mediaTypes.banner.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, - 'adomain': ['example.com'] - }] - }] - } - }; - }); - - 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, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - 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, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - 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/brightcomSSPBidAdapter_spec.js b/test/spec/modules/brightcomSSPBidAdapter_spec.js deleted file mode 100644 index 6f35a7a290b..00000000000 --- a/test/spec/modules/brightcomSSPBidAdapter_spec.js +++ /dev/null @@ -1,411 +0,0 @@ -import { expect } from 'chai'; -import * as utils from 'src/utils.js'; -import { spec } from 'modules/brightcomSSPBidAdapter'; -import { newBidder } from 'src/adapters/bidderFactory.js'; -import {config} from '../../../src/config'; - -const URL = 'https://rt.marphezis.com/hb'; - -describe('brightcomSSPBidAdapter', 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': 'bcmssp', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250], [300, 600]] - } - }, - 'bidId': '5fb26ac22bde4', - 'bidderRequestId': '4bf93aeb730cb9', - 'auctionId': 'ffe9a1f7-7b67-4bda-a8e0-9ee5dc9f442e', - 'schain': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ - { - 'asi': 'exchange1.com', - 'sid': '1234', - 'hp': 1, - 'rid': 'bid-request-1', - 'name': 'publisher', - 'domain': 'publisher.com' - } - ] - }, - }]; - - 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': 'bcmssp', - 'params': { - 'publisherId': 1234567 - }, - 'adUnitCode': 'adunit-code', - 'mediaTypes': { - 'banner': { - '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 publisherId 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].mediaTypes.banner.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); - }); - - it('sends gdpr info if exists', function () { - const consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; - const bidderRequest = { - 'bidderCode': 'bcmssp', - 'auctionId': '1d1a030790a437', - 'bidderRequestId': '22edbae2744bf5', - 'timeout': 3000, - gdprConsent: { - consentString: consentString, - gdprApplies: true - }, - refererInfo: { - page: 'http://example.com/page.html', - domain: 'example.com', - } - }; - 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); - }); - - it('sends us_privacy', function () { - const bidderRequest = { - uspConsent: '1YYY' - }; - const data = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data) - - expect(data.regs).to.not.equal(null); - expect(data.regs.ext).to.not.equal(null); - expect(data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('sends coppa', function () { - sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); - - const data = JSON.parse(spec.buildRequests(bidRequests).data) - expect(data.regs).to.not.be.undefined; - expect(data.regs.coppa).to.equal(1); - }); - - it('sends schain', function () { - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data).to.not.be.undefined; - expect(data.source).to.not.be.undefined; - expect(data.source.ext).to.not.be.undefined; - expect(data.source.ext.schain).to.not.be.undefined; - expect(data.source.ext.schain.complete).to.equal(1); - expect(data.source.ext.schain.ver).to.equal('1.0'); - expect(data.source.ext.schain.nodes).to.not.be.undefined; - expect(data.source.ext.schain.nodes).to.lengthOf(1); - expect(data.source.ext.schain.nodes[0].asi).to.equal('exchange1.com'); - expect(data.source.ext.schain.nodes[0].sid).to.equal('1234'); - expect(data.source.ext.schain.nodes[0].hp).to.equal(1); - expect(data.source.ext.schain.nodes[0].rid).to.equal('bid-request-1'); - expect(data.source.ext.schain.nodes[0].name).to.equal('publisher'); - expect(data.source.ext.schain.nodes[0].domain).to.equal('publisher.com'); - }); - - it('sends user eid parameters', function () { - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' - } - }] - } - ]; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.eids).to.not.be.undefined; - expect(data.user.ext.eids).to.deep.equal(bidRequests[0].userIdAsEids); - }); - - it('sends user id parameters', function () { - const userId = { - sharedid: { - id: '01*******', - third: '01E*******' - } - }; - - bidRequests[0].userId = userId; - - const data = JSON.parse(spec.buildRequests(bidRequests).data); - expect(data.user).to.not.be.undefined; - expect(data.user.ext).to.not.be.undefined; - expect(data.user.ext.ids).is.deep.equal(userId); - }); - - 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].mediaTypes.banner.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, - 'adomain': ['example.com'] - }] - }] - } - }; - }); - - 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, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - 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, - 'meta': { - 'advertiserDomains': ['example.com'] - } - }]; - - 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/britepoolIdSystem_spec.js b/test/spec/modules/britepoolIdSystem_spec.js deleted file mode 100644 index ddb61806006..00000000000 --- a/test/spec/modules/britepoolIdSystem_spec.js +++ /dev/null @@ -1,129 +0,0 @@ -import {britepoolIdSubmodule} from 'modules/britepoolIdSystem.js'; -import * as utils from '../../../src/utils.js'; - -describe('BritePool Submodule', () => { - const api_key = '1111'; - const aaid = '4421ea96-34a9-45df-a4ea-3c41a48a18b1'; - const idfa = '2d1c4fac-5507-4e28-991c-ca544e992dba'; - const bpid = '279c0161-5152-487f-809e-05d7f7e653fd'; - const url_override = 'https://override'; - const getter_override = function(params) { - return JSON.stringify({ 'primaryBPID': bpid }); - }; - const getter_callback_override = function(params) { - return callback => { - callback(JSON.stringify({ 'primaryBPID': bpid })); - }; - }; - - 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); - expect(headers['x-api-key']).to.equal(api_key); - expect(params).to.eql({ aaid }); - }); - - it('sends x-api-key in header and two identifiers', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, idfa }); - assert(errors.length === 0, errors); - expect(headers['x-api-key']).to.equal(api_key); - expect(params).to.eql({ aaid, idfa }); - }); - - it('allows call without api_key', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ aaid, idfa }); - expect(params).to.eql({ aaid, idfa }); - expect(errors.length).to.equal(0); - }); - - it('test url override', () => { - const { params, headers, url, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override }); - expect(url).to.equal(url_override); - // Making sure it did not become part of params - 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({ params: { api_key, aaid, url: url_override, getter: getter_override } }); - assert.deepEqual(response, { id: { 'primaryBPID': bpid } }); - }); - - it('test getter override with callback', done => { - const { params, headers, url, getter, errors } = britepoolIdSubmodule.createParams({ api_key, aaid, url: url_override, getter: getter_callback_override }); - 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({ 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 }); - done(); - }); - }); -}); diff --git a/test/spec/modules/browsiRtdProvider_spec.js b/test/spec/modules/browsiRtdProvider_spec.js index 5fcc78f4322..92a08c8fbef 100644 --- a/test/spec/modules/browsiRtdProvider_spec.js +++ b/test/spec/modules/browsiRtdProvider_spec.js @@ -1,9 +1,9 @@ import * as browsiRTD from '../../../modules/browsiRtdProvider.js'; -import {makeSlot} from '../integration/faker/googletag.js'; import * as utils from '../../../src/utils' import * as events from '../../../src/events'; import * as sinon from 'sinon'; import {sendPageviewEvent} from '../../../modules/browsiRtdProvider.js'; +import * as mockGpt from 'test/spec/integration/faker/googletag.js'; describe('browsi Real time data sub module', function () { const conf = { @@ -55,7 +55,7 @@ describe('browsi Real time data sub module', function () { }); it('should match placement with ad unit', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); const test1 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc']); // true const test2 = browsiRTD.isIdMatchingAdUnit(slot, ['/123/abc', '/456/def']); // true @@ -69,7 +69,7 @@ describe('browsi Real time data sub module', function () { }); it('should return correct macro values', function () { - const slot = makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); + const slot = mockGpt.makeSlot({code: '/123/abc', divId: 'browsiAd_1'}); slot.setTargeting('test', ['test', 'value']); // slot getTargeting doesn't act like GPT so we can't expect real value @@ -90,7 +90,7 @@ describe('browsi Real time data sub module', function () { }); it('should return prediction from server', function () { - makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); + mockGpt.makeSlot({code: 'hasPrediction', divId: 'hasPrediction'}); const data = { p: {'hasPrediction': {ps: {0: 0.234}}}, kn: 'bv', @@ -266,4 +266,29 @@ describe('browsi Real time data sub module', function () { expect(eventsEmitSpy.callCount).to.equal(0); }) }) + + describe('set targeting - invalid params', function () { + it('should return false if key is undefined', function () { + expect(browsiRTD.setKeyValue()).to.equal(false); + }) + it('should return false if key is not string', function () { + expect(browsiRTD.setKeyValue(1)).to.equal(false); + }) + }) + describe('set targeting - valid params', function () { + let slot; + const splitKey = 'splitTest'; + before(() => { + mockGpt.reset(); + window.googletag.pubads().clearTargeting(); + slot = mockGpt.makeSlot({code: '/123/split', divId: 'split'}); + browsiRTD.setKeyValue(splitKey); + window.googletag.cmd.forEach(cmd => cmd()); + }) + it('should place numeric key value on all slots', function () { + const targetingValue = window.googletag.pubads().getTargeting(splitKey); + expect(targetingValue).to.be.an('array').that.is.not.empty; + expect(targetingValue[0]).to.be.a('string'); + }) + }) }); diff --git a/test/spec/modules/c1xBidAdapter_spec.js b/test/spec/modules/c1xBidAdapter_spec.js index 315680cba26..c93b43d571b 100644 --- a/test/spec/modules/c1xBidAdapter_spec.js +++ b/test/spec/modules/c1xBidAdapter_spec.js @@ -31,9 +31,9 @@ describe('C1XAdapter', () => { }); it('should return false when require params are not passed', function () { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(c1xAdapter.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(c1xAdapter.isBidRequestValid(invalidBid)).to.equal(false); }); }); describe('buildRequests', () => { diff --git a/test/spec/modules/cadentApertureMXBidAdapter_spec.js b/test/spec/modules/cadentApertureMXBidAdapter_spec.js index 3ccb5405552..d8686e3c667 100644 --- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -48,49 +48,11 @@ describe('cadent_aperture_mx Adapter', function () { 'auctionId': '1d1a01234a475' }; let noBid = {}; - let otherBid = { - 'bidder': 'emxdigital', - 'params': { - 'tagid': '25251' - }, - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c2501de1e', - 'bidderRequestId': '22edbae3120bf6', - 'auctionId': '1d1a01234a475' - }; - let noMediaSizeBid = { - 'bidder': 'emxdigital', - 'params': { - 'tagid': '25251' - }, - 'mediaTypes': { - 'banner': {} - }, - 'adUnitCode': 'adunit-code', - 'sizes': [ - [300, 250], - [300, 600] - ], - 'bidId': '30b31c2501de1e', - 'bidderRequestId': '22edbae3120bf6', - 'auctionId': '1d1a01234a475' - }; it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); expect(spec.isBidRequestValid(badBid)).to.equal(false); expect(spec.isBidRequestValid(noBid)).to.equal(false); - expect(spec.isBidRequestValid(otherBid)).to.equal(false); - expect(spec.isBidRequestValid(noMediaSizeBid)).to.equal(false); }); }); @@ -451,10 +413,27 @@ describe('cadent_aperture_mx Adapter', function () { }); }); - it('should add gpid to request if present', () => { + it('should add gpid to request if present in ext.gpid', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { gpid, data: { adserver: { adslot: gpid + '1' }, pbadslot: gpid + '2' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.adserver.adslot', () => { + const gpid = '/12345/my-gpt-tag-0'; + let bid = utils.deepClone(bidderRequest.bids[0]); + bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid }, pbadslot: gpid + '1' } } }; + let requestWithGPID = spec.buildRequests([bid], bidderRequest); + requestWithGPID = JSON.parse(requestWithGPID.data); + expect(requestWithGPID.imp[0].ext.gpid).to.exist.and.equal(gpid); + }); + + it('should add gpid to request if present in ext.data.pbadslot', () => { const gpid = '/12345/my-gpt-tag-0'; let bid = utils.deepClone(bidderRequest.bids[0]); - bid.ortb2Imp = { ext: { data: { adserver: { adslot: gpid } } } }; bid.ortb2Imp = { ext: { data: { pbadslot: gpid } } }; let requestWithGPID = spec.buildRequests([bid], bidderRequest); requestWithGPID = JSON.parse(requestWithGPID.data); diff --git a/test/spec/modules/carodaBidAdapter_spec.js b/test/spec/modules/carodaBidAdapter_spec.js index f575e31e85d..bf4557d7a8a 100644 --- a/test/spec/modules/carodaBidAdapter_spec.js +++ b/test/spec/modules/carodaBidAdapter_spec.js @@ -197,17 +197,14 @@ describe('Caroda adapter', function () { let validBidRequests = [{ bid_id: 'bidId', params: {}, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE' - }) + userIdAsEids: [ + { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, + { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + ] }]; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } })[0].data); - assert.deepEqual(request.user.eids, [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } - ]); + assert.deepEqual(request.user.eids, validBidRequests[0].userIdAsEids); }); describe('user privacy', function () { diff --git a/test/spec/modules/ccxBidAdapter_spec.js b/test/spec/modules/ccxBidAdapter_spec.js index cbae441e7e7..1e345691bcf 100644 --- a/test/spec/modules/ccxBidAdapter_spec.js +++ b/test/spec/modules/ccxBidAdapter_spec.js @@ -1,4 +1,5 @@ import { expect } from 'chai'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; import { spec } from 'modules/ccxBidAdapter.js'; import * as utils from 'src/utils.js'; @@ -39,6 +40,7 @@ describe('ccxAdapter', function () { transactionId: 'aefddd38-cfa0-48ab-8bdd-325de4bab5f9' } ]; + describe('isBidRequestValid', function () { it('Valid bid requests', function () { expect(spec.isBidRequestValid(bids[0])).to.be.true; @@ -75,6 +77,7 @@ describe('ccxAdapter', function () { expect(spec.isBidRequestValid(bidsClone[0])).to.be.true; }); }); + describe('buildRequests', function () { it('No valid bids', function () { expect(spec.buildRequests([])).to.be.undefined; @@ -173,6 +176,7 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); + it('Valid bid request - sizes old style', function () { let bidsClone = utils.deepClone(bids); delete (bidsClone[0].mediaTypes); @@ -218,6 +222,7 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); + it('Valid bid request - sizes old style - no media type', function () { let bidsClone = utils.deepClone(bids); delete (bidsClone[0].mediaTypes); @@ -385,6 +390,7 @@ describe('ccxAdapter', function () { expect(spec.interpretResponse({})).to.be.empty; }); }); + describe('getUserSyncs', function () { it('Valid syncs - all', function () { let syncOptions = { @@ -434,6 +440,7 @@ describe('ccxAdapter', function () { expect(spec.getUserSyncs(syncOptions, [{body: response}])).to.be.empty; }); }); + describe('mediaTypesVideoParams', function () { it('Valid video mediaTypes', function () { let bids = [ @@ -488,4 +495,78 @@ describe('ccxAdapter', function () { expect(data.imp).to.deep.have.same.members(imps); }); }); + + describe('FLEDGE', function () { + it('should properly build a request when FLEDGE is enabled', function () { + let bidderRequest = { + paapi: { + enabled: true + } + }; + let bids = [ + { + adUnitCode: 'banner', + auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa1', + bidId: '2e56e1af51ccc1', + bidder: 'ccx', + bidderRequestId: '17e7b9f58accc1', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 609 + }, + sizes: [[300, 250]], + transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb1', + ortb2Imp: { + ext: { + ae: 1 + } + } + } + ]; + + let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let data = JSON.parse(ortbRequest.data); + expect(data.imp[0].ext.ae).to.equal(1); + }); + + it('should properly build a request when FLEDGE is disabled', function () { + let bidderRequest = { + paapi: { + enabled: false + } + }; + let bids = [ + { + adUnitCode: 'banner', + auctionId: '0b9de793-8eda-481e-a548-aaaaaaaaaaa2', + bidId: '2e56e1af51ccc2', + bidder: 'ccx', + bidderRequestId: '17e7b9f58accc2', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 610 + }, + sizes: [[300, 250]], + transactionId: 'befddd38-cfa0-48ab-8bdd-bbbbbbbbbbb2', + ortb2Imp: { + ext: { + ae: 1 + } + } + } + ]; + + let ortbRequest = spec.buildRequests(bids, syncAddFPDToBidderRequest(bidderRequest)); + let data = JSON.parse(ortbRequest.data); + expect(data.imp[0].ext.ae).to.be.undefined; + }); + }); }); diff --git a/test/spec/modules/ceeIdSystem_spec.js b/test/spec/modules/ceeIdSystem_spec.js new file mode 100644 index 00000000000..62ae2898ede --- /dev/null +++ b/test/spec/modules/ceeIdSystem_spec.js @@ -0,0 +1,131 @@ +import { ceeIdSubmodule, storage, readId } from 'modules/ceeIdSystem.js'; +import sinon from 'sinon'; + +describe('ceeIdSystem', () => { + let sandbox; + let getCookieStub; + let getDataFromLocalStorageStub; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + getCookieStub = sandbox.stub(storage, 'getCookie'); + getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId', () => { + it('should return an object with id when ceeIdToken is found in LS', () => { + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + }, + }; + + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(config); + + expect(result).to.deep.equal({ id: 'testToken' }); + }); + + it('should return an object with id when ceeIdToken is passed in setConfig', () => { + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + value: 'testTokenFromSetConfig' + }, + }; + + getDataFromLocalStorageStub.returns('testToken'); + getCookieStub.returns('testToken'); + + const result = ceeIdSubmodule.getId(config); + + expect(result).to.deep.equal({ id: 'testTokenFromSetConfig' }); + }); + + it('should return undefined when ceeIdToken is not found', () => { + const config = { + name: 'ceeId', + storage: { + type: 'cookie', + name: 'ceeIdToken', + expires: 7, + refreshInSeconds: 360, + }, + params: { + tokenName: 'WPxid', + }, + }; + const result = ceeIdSubmodule.getId(config); + + expect(result).to.be.undefined; + }); + }); + + describe('decode', () => { + it('should return an object with ceeId when value is a string', () => { + const result = ceeIdSubmodule.decode('testId'); + + expect(result).to.deep.equal({ ceeId: 'testId' }); + }); + + it('should return undefined when value is not a string', () => { + const result = ceeIdSubmodule.decode({}); + + expect(result).to.be.undefined; + }); + }); + + describe('readId', () => { + it('should return data from local storage when it exists', () => { + const tokenName = 'testToken'; + + getDataFromLocalStorageStub.returns('local_storage_data'); + + const result = readId(tokenName); + + expect(result).to.equal('local_storage_data'); + }); + + it('should return data from cookie when local storage data does not exist', () => { + const tokenName = 'testToken'; + + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns('cookie_data'); + + const result = readId(tokenName); + + expect(result).to.equal('cookie_data'); + }); + + it('should return null when neither local storage data nor cookie data exists', () => { + const tokenName = 'testToken'; + + getDataFromLocalStorageStub.returns(null); + getCookieStub.returns(null); + + const result = readId(tokenName); + + expect(result).to.be.null; + }); + }); +}); diff --git a/test/spec/modules/cleanioRtdProvider_spec.js b/test/spec/modules/cleanioRtdProvider_spec.js index 3145108c373..0211a9ae588 100644 --- a/test/spec/modules/cleanioRtdProvider_spec.js +++ b/test/spec/modules/cleanioRtdProvider_spec.js @@ -5,6 +5,7 @@ import * as events from '../../../src/events.js'; import { EVENTS } from '../../../src/constants.js'; import { __TEST__ } from '../../../modules/cleanioRtdProvider.js'; +import {MODULE_TYPE_RTD} from '../../../src/activities/modules.js'; const { readConfig, @@ -70,7 +71,7 @@ describe('clean.io RTD module', function () { pageInitStepProtectPage(fakeScriptURL); sinon.assert.calledOnce(loadExternalScriptStub); - sinon.assert.calledWith(loadExternalScriptStub, fakeScriptURL, 'clean.io'); + sinon.assert.calledWith(loadExternalScriptStub, fakeScriptURL, MODULE_TYPE_RTD, 'clean.io'); }); }); @@ -139,7 +140,7 @@ describe('clean.io RTD module', function () { const { init, onBidResponseEvent } = getModule(); expect(init({ params: { cdnUrl: 'https://abc1234567890.cloudfront.net/script.js', protectionMode: 'full' } }, {})).to.equal(true); sinon.assert.calledOnce(loadExternalScriptStub); - sinon.assert.calledWith(loadExternalScriptStub, 'https://abc1234567890.cloudfront.net/script.js', 'clean.io'); + sinon.assert.calledWith(loadExternalScriptStub, 'https://abc1234567890.cloudfront.net/script.js', MODULE_TYPE_RTD, 'clean.io'); const fakeBidResponse = makeFakeBidResponse(); onBidResponseEvent(fakeBidResponse, {}, {}); diff --git a/test/spec/modules/cleanmedianetBidAdapter_spec.js b/test/spec/modules/cleanmedianetBidAdapter_spec.js index 3c73dac07de..58270438772 100644 --- a/test/spec/modules/cleanmedianetBidAdapter_spec.js +++ b/test/spec/modules/cleanmedianetBidAdapter_spec.js @@ -376,7 +376,7 @@ describe('CleanmedianetAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.params.video = { - placement: 1, + plcmt: 1, minduration: 1, } @@ -395,7 +395,7 @@ describe('CleanmedianetAdapter', () => { 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.plcmt).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); @@ -405,7 +405,7 @@ describe('CleanmedianetAdapter', () => { playerSize: [302, 252], mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -428,7 +428,7 @@ describe('CleanmedianetAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -457,7 +457,7 @@ describe('CleanmedianetAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, diff --git a/test/spec/modules/clickforceBidAdapter_spec.js b/test/spec/modules/clickforceBidAdapter_spec.js index c4c4d77e954..99aef433890 100644 --- a/test/spec/modules/clickforceBidAdapter_spec.js +++ b/test/spec/modules/clickforceBidAdapter_spec.js @@ -31,12 +31,12 @@ describe('ClickforceAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/colombiaBidAdapter_spec.js b/test/spec/modules/colombiaBidAdapter_spec.js new file mode 100644 index 00000000000..1b61e1a92b4 --- /dev/null +++ b/test/spec/modules/colombiaBidAdapter_spec.js @@ -0,0 +1,155 @@ +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 invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).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': '"1f9c98192de2511"', + 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', + } + ]; + let bidderRequest = { + refererInfo: { + numIframes: 0, + reachedTop: true, + referer: 'http://example.com', + stack: ['http://example.com'] + } + }; + + const request = spec.buildRequests(bidRequests); + 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); + }); + }); + + 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 = [{ + 'ad': '
This is test case for colombia adapter
', + 'cpm': 3.14, + 'creativeId': '6b958110-612c-4b03-b6a9-7436c9f746dc-1sk24', + 'currency': 'USD', + 'requestId': '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 ebe1e9be4d4..22a98df633f 100644 --- a/test/spec/modules/colossussspBidAdapter_spec.js +++ b/test/spec/modules/colossussspBidAdapter_spec.js @@ -257,7 +257,6 @@ describe('ColossussspAdapter', function () { describe('buildRequests with user ids', function () { var clonedBid = JSON.parse(JSON.stringify(bid)); clonedBid.userId = {} - clonedBid.userId.britepoolid = 'britepoolid123'; clonedBid.userId.idl_env = 'idl_env123'; clonedBid.userId.tdid = 'tdid123'; clonedBid.userId.id5id = { uid: 'id5id123' }; @@ -303,11 +302,11 @@ describe('ColossussspAdapter', function () { let placement = placements[i]; expect(placement).to.have.property('eids') expect(placement.eids).to.be.an('array') - expect(placement.eids.length).to.be.equal(8) + expect(placement.eids.length).to.be.equal(7) 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(['pubcid.org', 'adserver.org', 'neustar.biz', 'britepool.com', 'identityLink', 'id5-sync.com', 'adserver.org', 'uidapi.com']) + expect(v.source).to.be.oneOf(['pubcid.org', 'adserver.org', 'neustar.biz', 'identityLink', 'id5-sync.com', 'adserver.org', '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') diff --git a/test/spec/modules/compassBidAdapter_spec.js b/test/spec/modules/compassBidAdapter_spec.js index 6a761e63ea1..d0cecc2272f 100644 --- a/test/spec/modules/compassBidAdapter_spec.js +++ b/test/spec/modules/compassBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/compassBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'compass' +const bidder = 'compass'; describe('CompassBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('CompassBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('CompassBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -112,6 +136,7 @@ describe('CompassBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -129,7 +154,7 @@ describe('CompassBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +170,56 @@ describe('CompassBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +245,10 @@ describe('CompassBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +263,38 @@ describe('CompassBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +498,17 @@ describe('CompassBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + expect(syncData).to.be.an('array').which.is.not.empty; + expect(syncData[0]).to.be.an('object') + expect(syncData[0].type).to.be.a('string') + expect(syncData[0].type).to.equal('image') + expect(syncData[0].url).to.be.a('string') + expect(syncData[0].url).to.equal('https://sa-cs.deliverimp.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/concertBidAdapter_spec.js b/test/spec/modules/concertBidAdapter_spec.js index 0a76ed3e62d..2fb43236081 100644 --- a/test/spec/modules/concertBidAdapter_spec.js +++ b/test/spec/modules/concertBidAdapter_spec.js @@ -249,6 +249,22 @@ describe('ConcertAdapter', function () { }); }); + it('should include dealId when present in bidResponse', function() { + const bids = spec.interpretResponse({ + body: { + bids: [ + { ...bidResponse.body.bids[0], dealid: 'CON-123' } + ] + } + }, bidRequest); + expect(bids[0]).to.have.property('dealId'); + }); + + it('should exclude dealId when absent in bidResponse', function() { + const bids = spec.interpretResponse(bidResponse, bidRequest); + expect(bids[0]).to.not.have.property('dealId'); + }); + 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); diff --git a/test/spec/modules/connatixBidAdapter_spec.js b/test/spec/modules/connatixBidAdapter_spec.js index 4d816c4e816..af56b937f58 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -1,9 +1,23 @@ import { expect } from 'chai'; +import sinon from 'sinon'; import { - spec, - getBidFloor as connatixGetBidFloor + _getBidRequests, + _canSelectViewabilityContainer as connatixCanSelectViewabilityContainer, + detectViewability as connatixDetectViewability, + getBidFloor as connatixGetBidFloor, + _getMinSize as connatixGetMinSize, + _getViewability as connatixGetViewability, + _isViewabilityMeasurable as connatixIsViewabilityMeasurable, + saveOnAllStorages as connatixSaveOnAllStorages, + readFromAllStorages as connatixReadFromAllStorages, + storage, + spec } from '../../../modules/connatixBidAdapter.js'; -import { BANNER } from '../../../src/mediaTypes.js'; +import adapterManager from '../../../src/adapterManager.js'; +import * as ajax from '../../../src/ajax.js'; +import { ADPOD, BANNER, VIDEO } from '../../../src/mediaTypes.js'; + +const BIDDER_CODE = 'connatix'; describe('connatixBidAdapter', function () { let bid; @@ -24,6 +38,478 @@ describe('connatixBidAdapter', function () { }; }; + function addVideoToBidMock(bid) { + const mediaTypes = { + video: { + context: 'instream', + w: 1280, + h: 720, + playerSize: [1280, 720], + placement: 1, + plcmt: 1, + api: [1, 2], + mimes: ['video/mp4', 'application/javascript'], + minduration: 30, + maxduration: 60, + startdelay: 0, + } + } + + bid.mediaTypes = mediaTypes; + } + + describe('connatixGetMinSize', () => { + it('should return the smallest size based on area', () => { + const sizes = [ + { w: 300, h: 250 }, + { w: 728, h: 90 }, + { w: 160, h: 600 } + ]; + const result = connatixGetMinSize(sizes); + expect(result).to.deep.equal({ w: 728, h: 90 }); + }); + + it('should handle an array with one size', () => { + const sizes = [{ w: 300, h: 250 }]; + const result = connatixGetMinSize(sizes); + expect(result).to.deep.equal({ w: 300, h: 250 }); + }); + + it('should handle empty array', () => { + const sizes = []; + const result = connatixGetMinSize(sizes); + expect(result).to.be.undefined; + }); + }); + + describe('_isIframe', () => { + let querySelectorStub; + + beforeEach(() => { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + }); + + afterEach(() => { + querySelectorStub.restore(); + }); + + it('should return true when window.top.document.querySelector does not throw an error', () => { + querySelectorStub.returns({}); + expect(connatixCanSelectViewabilityContainer()).to.be.true; + }); + + it('should return false when window.top.document.querySelector throws an error', () => { + querySelectorStub.throws(new Error('test error')); + expect(connatixCanSelectViewabilityContainer()).to.be.false; + }); + }); + + describe('_isViewabilityMeasurable', () => { + let querySelectorStub; + + beforeEach(() => { + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + }); + + afterEach(() => { + querySelectorStub.restore(); + }); + + it('should return false if the element is null or undefined', () => { + expect(connatixIsViewabilityMeasurable(null)).to.be.false; + expect(connatixIsViewabilityMeasurable(undefined)).to.be.false; + }); + + it('should return false if _isIframe returns true', () => { + querySelectorStub.throws(new Error('test error')); + + const element = document.createElement('div'); + expect(connatixIsViewabilityMeasurable(element)).to.be.false; + }); + + it('should return true if _isIframe returns false', () => { + querySelectorStub.returns(document.createElement('div')) + + const element = document.createElement('div'); + expect(connatixIsViewabilityMeasurable(element)).to.be.true; + }); + }); + + describe('_getViewability', () => { + let element; + let getBoundingClientRectStub; + let topWinMock; + + beforeEach(() => { + element = document.createElement('div'); + getBoundingClientRectStub = sinon.stub(element, 'getBoundingClientRect'); + + topWinMock = { + document: { + visibilityState: 'visible' + }, + innerWidth: 800, + innerHeight: 600 + }; + }); + + afterEach(() => { + getBoundingClientRectStub.restore(); + }); + + it('should return 0 if the document is not visible', () => { + topWinMock.document.visibilityState = 'hidden'; + + const viewability = connatixGetViewability(element, topWinMock); + + expect(viewability).to.equal(0); + }); + + it('should return 100% if the element is fully in view', () => { + const boundingBox = { left: 100, top: 100, right: 300, bottom: 300, width: 200, height: 200 }; + getBoundingClientRectStub.returns(boundingBox); + + const viewability = connatixGetViewability(element, topWinMock); + + expect(viewability).to.equal(100); + }); + + it('should return the correct percentage if the element is partially in view', () => { + const boundingBox = { left: 700, top: 500, right: 900, bottom: 700, width: 200, height: 200 }; + getBoundingClientRectStub.returns(boundingBox); + + const viewability = connatixGetViewability(element, topWinMock); + + expect(viewability).to.equal(25); // 100x100 / 200x200 = 0.25 -> 25% + }); + + it('should return 0% if the element is not in view', () => { + const boundingBox = { left: 900, top: 700, right: 1100, bottom: 900, width: 200, height: 200 }; + getBoundingClientRectStub.returns(boundingBox); + + const viewability = connatixGetViewability(element, topWinMock); + + expect(viewability).to.equal(0); + }); + + it('should use provided width and height if element dimensions are zero', () => { + const boundingBox = { left: 100, top: 100, right: 100, bottom: 100, width: 0, height: 0 }; + getBoundingClientRectStub.returns(boundingBox); + + const dimensions = { w: 200, h: 200 }; + const viewability = connatixGetViewability(element, topWinMock, dimensions); + + expect(viewability).to.equal(100); // Element fully in view with provided dimensions + }); + }); + + describe('detectViewability', () => { + let element; + let getBoundingClientRectStub; + let topWinMock; + let querySelectorStub; + let getElementByIdStub; + + beforeEach(() => { + element = document.createElement('div'); + getBoundingClientRectStub = sinon.stub(element, 'getBoundingClientRect'); + + topWinMock = { + document: { + visibilityState: 'visible' + }, + innerWidth: 800, + innerHeight: 600 + }; + + querySelectorStub = sinon.stub(window.top.document, 'querySelector'); + getElementByIdStub = sinon.stub(document, 'getElementById'); + }); + + afterEach(() => { + getBoundingClientRectStub.restore(); + querySelectorStub.restore(); + getElementByIdStub.restore(); + }); + + it('should return 100% viewability when the element is fully within view and has a valid viewabilityContainerIdentifier', () => { + const bid = { + params: { viewabilityContainerIdentifier: '#validElement' }, + adUnitCode: 'adUnitCode123', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]] + }; + + getBoundingClientRectStub.returns({ + left: 100, + top: 100, + right: 400, + bottom: 350, + width: 300, + height: 250 + }); + + querySelectorStub.withArgs('#validElement').returns(element); + getElementByIdStub.returns(null); + + const result = connatixDetectViewability(bid); + + // Expected calculation: the element is fully in view, so 100% viewability + expect(result).to.equal(100); + }); + + it('should fall back to using bid sizes and adUnitCode when the viewabilityContainerIdentifier is invalid or was not provided', () => { + const bid = { + params: { viewabilityContainerIdentifier: '#invalidElement' }, + adUnitCode: 'adUnitCode123', + mediaTypes: { banner: { sizes: [[300, 250]] } }, + sizes: [[300, 250]] + }; + + getBoundingClientRectStub.returns({ + left: 200, + top: 100, + right: 500, + bottom: 350, + width: 300, + height: 250 + }); + + querySelectorStub.withArgs('#invalidElement').returns(null); + getElementByIdStub.withArgs('adUnitCode123').returns(element); + + const result = connatixDetectViewability(bid); + + expect(result).to.equal(100); // Full viewability + }); + + it('should use the adUnitCode as a fallback when querying an element fails due to a browser error, and return 100% viewability because adUnitCode container is fully in view', () => { + const bid = { + params: { viewabilityContainerIdentifier: '#invalidElement' }, + adUnitCode: 'adUnitCode123', + sizes: [[300, 250]] + }; + + // Simulate an error when querying the element + querySelectorStub.withArgs('#invalidElement').throws(new Error('Query failed')); + + getBoundingClientRectStub.returns({ + left: 100, + top: 100, + right: 400, + bottom: 350, + width: 300, + height: 250 + }); + + // The fallback should use the adUnitCode to find the element + getElementByIdStub.withArgs('adUnitCode123').returns(element); + + const result = connatixDetectViewability(bid); + + expect(result).to.equal(100); // Expect the fallback to work and return 100% viewability + }); + + it('should return null when querying the element by the provided identifier fails and the adUnitCode viewability container is unavailable', () => { + const bid = { + params: { viewabilityContainerIdentifier: '#invalidElement' }, + adUnitCode: 'adUnitCode123', + sizes: [[300, 250]] + }; + + // Simulate an error when querying the element + querySelectorStub.withArgs('#invalidElement').throws(new Error('Query failed')); + + getBoundingClientRectStub.returns({ + left: 100, + top: 100, + right: 400, + bottom: 350, + width: 300, + height: 250 + }); + + const result = connatixDetectViewability(bid); + + expect(result).to.equal(null); + }); + }); + + describe('_getBidRequests', function () { + let bid; + + // Mock a bid request similar to the one already used in connatixBidAdapter tests + function mockBidRequest() { + const mediaTypes = { + banner: { + sizes: [16, 9], + } + }; + return { + bidId: 'testing', + bidder: 'connatix', + params: { + placementId: '30e91414-545c-4f45-a950-0bec9308ff22', + viewabilityContainerIdentifier: 'viewabilityId', + }, + mediaTypes, + sizes: [300, 250] + }; + } + + it('should map valid bid requests and include the expected fields', function () { + bid = mockBidRequest(); + + const result = _getBidRequests([bid]); + + expect(result).to.have.lengthOf(1); + expect(result[0]).to.have.property('bidId', bid.bidId); + expect(result[0]).to.have.property('mediaTypes', bid.mediaTypes); + expect(result[0]).to.have.property('sizes', bid.sizes); + expect(result[0]).to.have.property('placementId', bid.params.placementId); + expect(result[0]).to.have.property('hasViewabilityContainerId', true); + }); + + it('should set hasViewabilityContainerId to false when viewabilityContainerIdentifier is absent', function () { + bid = mockBidRequest(); + delete bid.params.viewabilityContainerIdentifier; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('hasViewabilityContainerId', false); + }); + + it('should call getBidFloor for each bid and return the correct floor value', function () { + bid = mockBidRequest(); + const floorValue = 5; + + // Mock getFloor method on bid + bid.getFloor = function() { + return { floor: floorValue }; + }; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('floor', floorValue); + }); + + it('should return floor as 0 if getBidFloor throws an error', function () { + bid = mockBidRequest(); + + // Mock getFloor method to throw an error + bid.getFloor = function() { + throw new Error('error'); + }; + + const result = _getBidRequests([bid]); + + expect(result[0]).to.have.property('floor', 0); + }); + }); + + describe('onTimeout', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'triggerEvent') + }) + + afterEach(() => { + ajaxStub.restore() + }); + + it('call event if bidder is connatix', () => { + const result = spec.onTimeout([{ + bidder: 'connatix', + timeout: 500, + }]); + expect(ajaxStub.calledOnce).to.equal(true); + + const data = ajaxStub.firstCall.args[0]; + expect(data.type).to.equal('Timeout'); + expect(data.timeout).to.equal(500); + }); + + it('timeout event is not triggered if bidder is not connatix', () => { + const result = spec.onTimeout([{ + bidder: 'otherBidder', + timeout: 500, + }]); + expect(ajaxStub.notCalled).to.equal(true); + }); + }); + + describe('onBidWon', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(spec, 'triggerEvent'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('calls triggerEvent with correct data when bidWinData is provided', () => { + const bidWinData = { + bidder: 'connatix', + cpm: 2.5, + requestId: 'abc123', + bidId: 'dasdas-dsawda-dwaddw-dwdwd', + adUnitCode: 'adunit_1', + timeToRespond: 300, + auctionId: 'auction_456', + }; + + spec.onBidWon(bidWinData); + expect(ajaxStub.calledOnce).to.equal(true); + + const eventData = ajaxStub.firstCall.args[0]; + expect(eventData.type).to.equal('BidWon'); + expect(eventData.bestBidBidder).to.equal('connatix'); + expect(eventData.bestBidPrice).to.equal(2.5); + expect(eventData.requestId).to.equal('abc123'); + expect(eventData.bidId).to.equal('dasdas-dsawda-dwaddw-dwdwd'); + expect(eventData.adUnitCode).to.equal('adunit_1'); + expect(eventData.timeToRespond).to.equal(300); + expect(eventData.auctionId).to.equal('auction_456'); + }); + + it('does not call triggerEvent if bidWinData is null', () => { + spec.onBidWon(null); + expect(ajaxStub.notCalled).to.equal(true); + }); + + it('does not call triggerEvent if bidWinData is undefined', () => { + spec.onBidWon(undefined); + expect(ajaxStub.notCalled).to.equal(true); + }); + }); + + describe('triggerEvent', function () { + let ajaxStub; + + beforeEach(() => { + ajaxStub = sinon.stub(ajax, 'ajax'); + }); + + afterEach(() => { + ajaxStub.restore(); + }); + + it('should call ajax with the correct parameters', () => { + const data = { type: 'BidWon', bestBidBidder: 'bidder1', bestBidPrice: 1.5, requestId: 'req123', adUnitCode: 'ad123', timeToRespond: 250, auctionId: 'auc123', context: {} }; + spec.triggerEvent(data); + + expect(ajaxStub.calledOnce).to.equal(true); + const [url, _, payload, options] = ajaxStub.firstCall.args; + expect(url).to.equal('https://capi.connatix.com/tr/am'); + expect(payload).to.equal(JSON.stringify(data)); + expect(options.method).to.equal('POST'); + expect(options.withCredentials).to.equal(false); + }); + }); + describe('isBidRequestValid', function () { this.beforeEach(function () { bid = mockBidRequest(); @@ -32,10 +518,6 @@ describe('connatixBidAdapter', function () { it('Should return true if all required fileds are present', function () { expect(spec.isBidRequestValid(bid)).to.be.true; }); - it('Should return false if bidder does not correspond', function () { - bid.bidder = 'abc'; - expect(spec.isBidRequestValid(bid)).to.be.false; - }); it('Should return false if bidId is missing', function () { delete bid.bidId; expect(spec.isBidRequestValid(bid)).to.be.false; @@ -52,7 +534,7 @@ describe('connatixBidAdapter', function () { delete bid.mediaTypes; expect(spec.isBidRequestValid(bid)).to.be.false; }); - it('Should return false if banner is missing from mediaTypes ', function () { + it('Should return false if both banner and video are missing from mediaTypes', function () { delete bid.mediaTypes.banner; expect(spec.isBidRequestValid(bid)).to.be.false; }); @@ -68,6 +550,15 @@ describe('connatixBidAdapter', function () { bid.mediaTypes.banner.sizes = []; expect(spec.isBidRequestValid(bid)).to.be.false; }); + it('Should return true if video is set correctly', function () { + addVideoToBidMock(bid); + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + it('Should return false if context is set to adpod on video media type', function() { + addVideoToBidMock(bid); + bid.mediaTypes.video.context = ADPOD; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); it('Should return true if add an extra field was added to the bidRequest', function () { bid.params.test = 1; expect(spec.isBidRequestValid(bid)).to.be.true; @@ -76,6 +567,7 @@ describe('connatixBidAdapter', function () { describe('buildRequests', function () { let serverRequest; + let setCookieStub, setDataInLocalStorageStub; let bidderRequest = { refererInfo: { canonicalUrl: '', @@ -104,10 +596,21 @@ describe('connatixBidAdapter', function () { }; this.beforeEach(function () { + const mockIdentityProviderData = { mockKey: 'mockValue' }; + const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; + setCookieStub = sinon.stub(storage, 'setCookie'); + setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); + connatixSaveOnAllStorages('test_ids_cnx', mockIdentityProviderData, CNX_IDS_EXPIRY); + bid = mockBidRequest(); serverRequest = spec.buildRequests([bid], bidderRequest); }) + this.afterEach(function() { + setCookieStub.restore(); + setDataInLocalStorageStub.restore(); + }); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; @@ -134,6 +637,7 @@ describe('connatixBidAdapter', function () { expect(serverRequest.data.uspConsent).to.equal(bidderRequest.uspConsent); expect(serverRequest.data.gppConsent).to.equal(bidderRequest.gppConsent); expect(serverRequest.data.ortb2).to.equal(bidderRequest.ortb2); + expect(serverRequest.data.identityProviderData).to.deep.equal({ mockKey: 'mockValue' }); }); }); @@ -197,6 +701,30 @@ describe('connatixBidAdapter', function () { expect(bidResponses[0].cpm).to.equal(firstBidCpm); expect(bidResponses[1].cpm).to.equal(secondBidCpm); }); + + it('Should contain specific values for banner bids', function () { + const adHtml = 'ad html' + serverResponse.body.Bids = [ { ...Bid, Ad: adHtml } ]; + + const bidResponses = spec.interpretResponse(serverResponse); + const [ bidResponse ] = bidResponses; + + expect(bidResponse.vastXml).to.be.undefined; + expect(bidResponse.ad).to.equal(adHtml); + expect(bidResponse.mediaType).to.equal(BANNER); + }); + + it('Should contain specific values for video bids', function () { + const adVastXml = 'ad vast xml' + serverResponse.body.Bids = [ { ...Bid, VastXml: adVastXml } ]; + + const bidResponses = spec.interpretResponse(serverResponse); + const [ bidResponse ] = bidResponses; + + expect(bidResponse.ad).to.be.undefined; + expect(bidResponse.vastXml).to.equal(adVastXml); + expect(bidResponse.mediaType).to.equal(VIDEO); + }); }); describe('getUserSyncs', function() { @@ -303,6 +831,77 @@ describe('connatixBidAdapter', function () { }); }); + describe('userIdAsEids', function() { + let validBidRequests; + + this.beforeEach(function () { + bid = mockBidRequest(); + validBidRequests = [bid]; + }) + + it('Connatix adapter reads EIDs from Prebid user models and adds it to Request', function() { + validBidRequests[0].userIdAsEids = [{ + 'source': 'adserver.org', + 'uids': [{ + 'id': 'TTD_ID_FROM_USER_ID_MODULE', + 'atype': 1, + 'ext': { + 'stype': 'ppuid', + 'rtiPartner': 'TDID' + } + }] + }, + { + 'source': 'pubserver.org', + 'uids': [{ + 'id': 'TDID_FROM_USER_ID_MODULE', + 'atype': 1 + }] + }]; + let serverRequest = spec.buildRequests(validBidRequests, {}); + expect(serverRequest.data.userIdList).to.deep.equal(validBidRequests[0].userIdAsEids); + }); + }); + + describe('isConnatix', function () { + let aliasRegistryStub; + + beforeEach(() => { + aliasRegistryStub = sinon.stub(adapterManager, 'aliasRegistry').value({}); + }); + + afterEach(() => { + aliasRegistryStub.restore(); + }); + + it('should return false if aliasName is undefined or null', () => { + expect(spec.isConnatix(undefined)).to.be.false; + expect(spec.isConnatix(null)).to.be.false; + }); + + it('should return true if aliasName matches BIDDER_CODE', () => { + const aliasName = BIDDER_CODE; + expect(spec.isConnatix(aliasName)).to.be.true; + }); + + it('should return true if aliasName is mapped to BIDDER_CODE in aliasRegistry', () => { + const aliasName = 'connatixAlias'; + aliasRegistryStub.value({ 'connatixAlias': BIDDER_CODE }); + expect(spec.isConnatix(aliasName)).to.be.true; + }); + + it('should return false if aliasName does not match BIDDER_CODE', () => { + const aliasName = 'otherBidder'; + expect(spec.isConnatix(aliasName)).to.be.false; + }); + + it('should return false if aliasName is mapped to a different bidder in aliasRegistry', () => { + const aliasName = 'someOtherAlias'; + aliasRegistryStub.value({ 'someOtherAlias': 'otherBidder' }); + expect(spec.isConnatix(aliasName)).to.be.false; + }); + }); + describe('getBidFloor', function () { this.beforeEach(function () { bid = mockBidRequest(); @@ -352,4 +951,102 @@ describe('connatixBidAdapter', function () { expect(floor).to.equal(0); }); }); + describe('getUserSyncs with message event listener', function() { + const CNX_IDS_EXPIRY = 24 * 30 * 60 * 60 * 1000; + const CNX_IDS_LOCAL_STORAGE_COOKIES_KEY = 'cnx_user_ids'; + const ALL_PROVIDERS_RESOLVED_EVENT = 'cnx_all_identity_providers_resolved'; + + const mockData = { + providerName: 'nonId', + data: { + supplementalEids: [{ provider: 2, group: 1, eidsList: ['123', '456'] }] + } + }; + + function messageHandler(event) { + if (!event.data || event.origin !== 'https://cds.connatix.com') { + return; + } + + if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT) { + window.removeEventListener('message', messageHandler); + event.stopImmediatePropagation(); + } + + if (event.data.type === ALL_PROVIDERS_RESOLVED_EVENT || event.data.type === IDENTITY_PROVIDER_RESOLVED_EVENT) { + const response = event.data; + if (response.data) { + connatixSaveOnAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, response.data, CNX_IDS_EXPIRY); + } + } + } + + let sandbox; + + beforeEach(() => { + sandbox = sinon.createSandbox(); + sandbox.stub(storage, 'setCookie'); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(window, 'removeEventListener'); + sandbox.stub(storage, 'cookiesAreEnabled').returns(true); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getCookie'); + sandbox.stub(storage, 'getDataFromLocalStorage'); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('Should set a cookie and save to local storage when a valid message is received', () => { + const fakeEvent = { + data: { type: 'cnx_all_identity_providers_resolved', data: mockData }, + origin: 'https://cds.connatix.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.calledOnce).to.be.true; + expect(window.removeEventListener.calledWith('message', messageHandler)).to.be.true; + expect(storage.setCookie.calledWith(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, JSON.stringify(mockData), sinon.match.string)).to.be.true; + expect(storage.setDataInLocalStorage.calledWith(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY, JSON.stringify(mockData))).to.be.true; + + storage.getCookie.returns(JSON.stringify(mockData)); + storage.getDataFromLocalStorage.returns(JSON.stringify(mockData)); + + const retrievedData = connatixReadFromAllStorages(CNX_IDS_LOCAL_STORAGE_COOKIES_KEY); + expect(retrievedData).to.deep.equal(mockData); + }); + + it('Should should not do anything when there is no data in the payload', () => { + const fakeEvent = { + data: null, + origin: 'https://cds.connatix.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true; + expect(window.removeEventListener.notCalled).to.be.true; + expect(storage.setCookie.notCalled).to.be.true; + expect(storage.setDataInLocalStorage.notCalled).to.be.true; + }); + + it('Should should not do anything when the origin is invalid', () => { + const fakeEvent = { + data: { type: 'cnx_all_identity_providers_resolved', data: mockData }, + origin: 'https://notConnatix.com', + stopImmediatePropagation: sinon.spy() + }; + + messageHandler(fakeEvent); + + expect(fakeEvent.stopImmediatePropagation.notCalled).to.be.true; + expect(window.removeEventListener.notCalled).to.be.true; + expect(storage.setCookie.notCalled).to.be.true; + expect(storage.setDataInLocalStorage.notCalled).to.be.true; + }); + }); }); diff --git a/test/spec/modules/connectIdSystem_spec.js b/test/spec/modules/connectIdSystem_spec.js index 686c3d63a63..3009ecef769 100644 --- a/test/spec/modules/connectIdSystem_spec.js +++ b/test/spec/modules/connectIdSystem_spec.js @@ -549,24 +549,28 @@ describe('Yahoo ConnectID Submodule', () => { expect(result.callback).to.be.a('function'); }); + function mockOptout(value) { + getLocalStorageStub.callsFake((key) => { + if (key === 'connectIdOptOut') return value; + }) + } + it('returns an undefined if the Yahoo specific opt-out key is present in local storage', () => { - localStorage.setItem('connectIdOptOut', '1'); + mockOptout('1'); expect(invokeGetIdAPI({ he: HASHED_EMAIL, pixelId: PIXEL_ID }, consentData)).to.be.undefined; - localStorage.removeItem('connectIdOptOut'); }); it('returns an object with the callback function if the correct params are passed and Yahoo opt-out value is not "1"', () => { - localStorage.setItem('connectIdOptOut', 'true'); + mockOptout('true'); 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'); - localStorage.removeItem('connectIdOptOut'); }); it('Makes an ajax GET request to the production API endpoint with pixelId and he query params', () => { @@ -804,6 +808,25 @@ describe('Yahoo ConnectID Submodule', () => { }); }); }); + describe('userHasOptedOut()', () => { + it('should return a function', () => { + expect(connectIdSubmodule.userHasOptedOut).to.be.a('function'); + }); + + it('should return false when local storage key has not been set function', () => { + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); + + it('should return true when local storage key has been set to "1"', () => { + getLocalStorageStub.returns('1'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.true; + }); + + it('should return false when local storage key has not been set to "1"', () => { + getLocalStorageStub.returns('hello'); + expect(connectIdSubmodule.userHasOptedOut()).to.be.false; + }); + }); }); describe('decode()', () => { @@ -884,28 +907,4 @@ describe('Yahoo ConnectID Submodule', () => { })).to.be.true; }); }); - - describe('userHasOptedOut()', () => { - afterEach(() => { - localStorage.removeItem('connectIdOptOut'); - }); - - it('should return a function', () => { - expect(connectIdSubmodule.userHasOptedOut).to.be.a('function'); - }); - - it('should return false when local storage key has not been set function', () => { - expect(connectIdSubmodule.userHasOptedOut()).to.be.false; - }); - - it('should return true when local storage key has been set to "1"', () => { - localStorage.setItem('connectIdOptOut', '1'); - expect(connectIdSubmodule.userHasOptedOut()).to.be.true; - }); - - it('should return false when local storage key has not been set to "1"', () => { - localStorage.setItem('connectIdOptOut', 'hello'); - expect(connectIdSubmodule.userHasOptedOut()).to.be.false; - }); - }); }); diff --git a/test/spec/modules/connectadBidAdapter_spec.js b/test/spec/modules/connectadBidAdapter_spec.js index d8dfcb0ce98..f067d801c5e 100644 --- a/test/spec/modules/connectadBidAdapter_spec.js +++ b/test/spec/modules/connectadBidAdapter_spec.js @@ -2,6 +2,7 @@ import {expect} from 'chai'; import {spec} from 'modules/connectadBidAdapter.js'; import { config } from 'src/config.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import assert from 'assert'; describe('ConnectAd Adapter', function () { let bidRequests; @@ -26,7 +27,13 @@ describe('ConnectAd Adapter', function () { bidId: '2f95c00074b931', auctionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', bidderRequestId: '1c56ad30b9b8ca8', - transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df' + transactionId: 'e76cbb58-f3e1-4ad9-9f4c-718c1919d0df', + ortb2Imp: { + ext: { + tid: '601bda1a-01a9-4de9-b8f3-649d3bdd0d8f', + gpid: '/12345/homepage-leftnav' + } + }, } ]; @@ -69,6 +76,10 @@ describe('ConnectAd Adapter', function () { } }); + afterEach(function () { + config.resetConfig(); + }); + describe('inherited functions', function () { it('should exists and is a function', function () { const adapter = newBidder(spec); @@ -193,30 +204,28 @@ describe('ConnectAd Adapter', function () { }); it('should build a request if Consent but no gdprApplies', function () { - let bidderRequest = { + let localbidderRequest = { timeout: 3000, gdprConsent: { gdprApplies: false, consentString: 'consentDataString', }, } - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, localbidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.placements[0].adTypes).to.be.an('array'); expect(requestparse.placements[0].siteId).to.equal(123456); expect(requestparse.user.ext.consent).to.equal('consentDataString'); }); it('should build a request if gdprConsent empty', function () { - let bidderRequest = { + let localbidderRequest = { timeout: 3000, gdprConsent: {} } - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, localbidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.placements[0].adTypes).to.be.an('array'); expect(requestparse.placements[0].siteId).to.equal(123456); }); @@ -239,7 +248,7 @@ describe('ConnectAd Adapter', function () { it('should not include schain when not provided', function () { const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.source).to.not.exist; + expect(requestparse).to.not.have.property('source.ext.schain'); }); it('should submit coppa if set in config', function () { @@ -248,7 +257,17 @@ describe('ConnectAd Adapter', function () { .returns(true); const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.user.coppa).to.equal(1); + expect(requestparse.regs.coppa).to.equal(1); + config.getConfig.restore(); + }); + + it('should not set coppa when coppa is not provided or is set to false', function () { + sinon.stub(config, 'getConfig') + .withArgs('coppa') + .returns(false); + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + assert.equal(requestparse.regs.coppa, undefined); config.getConfig.restore(); }); @@ -259,27 +278,96 @@ describe('ConnectAd Adapter', function () { expect(requestparse.user.ext.eids[0].uids[0].id).to.equal('123456'); }); - it('should add referer info', function () { - const bidRequest = Object.assign({}, bidRequests[0]) - const bidderRequ = { - refererInfo: { - page: 'https://connectad.io/page.html', - legacy: { - referer: 'https://connectad.io/page.html', - reachedTop: true, - numIframes: 2, - stack: [ - 'https://connectad.io/page.html', - 'https://connectad.io/iframe1.html', - 'https://connectad.io/iframe2.html' - ] + it('should include DSA signals', function () { + const dsa = { + dsarequired: 3, + pubrender: 0, + datatopub: 2, + transparency: [ + { + domain: 'domain1.com', + dsaparams: [1] + }, + { + domain: 'domain2.com', + dsaparams: [1, 2] + } + ] + }; + + let bidRequest = { + ortb2: { + regs: { + ext: { + dsa + } + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + assert.deepEqual(data.regs.ext.dsa, dsa); + }); + + it('should pass auction level tid', function() { + const bidRequest = Object.assign([], bidRequests); + + const localBidderRequest = { + ...bidderRequest, + ortb2: { + source: { + tid: '9XSL9B79XM' } } } - const request = spec.buildRequests([bidRequest], bidderRequ); + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.source?.tid).to.equal('9XSL9B79XM') + }); + + it('should pass gpid', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].gpid).to.equal('/12345/homepage-leftnav'); + }); + + it('should pass impression level tid', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); + const requestparse = JSON.parse(request.data); + expect(requestparse.placements[0].tid).to.equal('601bda1a-01a9-4de9-b8f3-649d3bdd0d8f'); + }); + + it('should pass first party data', function() { + const bidRequest = Object.assign([], bidRequests); + + const localBidderRequest = { + ...bidderRequest, + ortb2: { + bcat: ['IAB1', 'IAB2-1'], + badv: ['xyz.com', 'zyx.com'], + site: { ext: { data: 'some site data' } }, + device: { ext: { data: 'some device data' } }, + user: { ext: { data: 'some user data' } }, + regs: { ext: { data: 'some regs data' } } + } + }; + + const request = spec.buildRequests(bidRequest, localBidderRequest); + const data = JSON.parse(request.data); + expect(data.bcat).to.deep.equal(localBidderRequest.ortb2.bcat); + expect(data.badv).to.deep.equal(localBidderRequest.ortb2.badv); + expect(data.site).to.nested.include({'ext.data': 'some site data'}); + expect(data.device).to.nested.include({'ext.data': 'some device data'}); + expect(data.user).to.nested.include({'ext.data': 'some user data'}); + expect(data.regs).to.nested.include({'ext.data': 'some regs data'}); + }); + + it('should accept tmax from global config if not set by requestBids method', function() { + const request = spec.buildRequests(bidRequests, bidderRequest); const requestparse = JSON.parse(request.data); - expect(requestparse.referrer_info).to.exist; + expect(requestparse.tmax).to.deep.equal(3000); }); it('should populate schain', function () { @@ -313,6 +401,76 @@ describe('ConnectAd Adapter', function () { }); }); + describe('GPP Implementation', function() { + it('should check with GPP Consent', function () { + let bidRequest = { + gppConsent: { + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'fullGppData': { + 'sectionId': 3, + 'gppVersion': 1, + 'sectionList': [ + 5, + 7 + ], + 'applicableSections': [ + 5 + ], + 'gppString': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'pingData': { + 'cmpStatus': 'loaded', + 'gppVersion': '1.0', + 'cmpDisplayStatus': 'visible', + 'supportedAPIs': [ + 'tcfca', + 'usnat', + 'usca', + 'usva', + 'usco', + 'usut', + 'usct' + ], + 'cmpId': 31 + }, + 'eventName': 'sectionChange' + }, + 'applicableSections': [ + 5 + ], + 'apiVersion': 1 + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + + it('should check without GPP Consent', function () { + let bidRequest = {}; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal(undefined); + }); + + it('should check with GPP Consent read from OpenRTB2', function () { + let bidRequest = { + ortb2: { + regs: { + 'gpp': 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', + 'gpp_sid': [ + 5 + ] + } + } + }; + let request = spec.buildRequests(bidRequests, bidRequest); + let data = JSON.parse(request.data); + expect(data.regs.gpp).to.equal('DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'); + expect(data.regs.gpp_sid[0]).to.equal(5); + }); + }); + describe('bid responses', function () { it('should return complete bid response with adomain', function () { const ADOMAINS = ['connectad.io']; @@ -348,6 +506,53 @@ describe('ConnectAd Adapter', function () { expect(bids[0].meta.advertiserDomains).to.deep.equal(ADOMAINS); }); + it('should process meta response object', function () { + const ADOMAINS = ['connectad.io']; + const dsa = { + behalf: 'Advertiser', + paid: 'Advertiser', + transparency: [{ + domain: 'dsp1domain.com', + dsaparams: [1, 2] + }], + adrender: 1 + }; + + let serverResponse = { + body: { + decisions: { + '2f95c00074b931': { + adId: '0', + adomain: ['connectad.io'], + contents: [ + { + body: '<<<---- Creative --->>>' + } + ], + height: '250', + width: '300', + dsa: dsa, + category: 'IAB123', + 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); + expect(bids[0].meta.dsa).to.equal(dsa); + expect(bids[0].meta.primaryCatId).to.equal('IAB123'); + }); + it('should return complete bid response with empty adomain', function () { const ADOMAINS = []; @@ -471,22 +676,59 @@ describe('ConnectAd Adapter', function () { }); }); + describe('GPP Sync', function() { + it('should concatenate gppString and applicableSections values in the returned image url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5] }; + const result = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'image', + url: `https://sync.connectad.io/ImageSyncer?gpp=DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN&gpp_sid=5&` + }]); + }); + + it('should concatenate gppString and applicableSections values in the returned iFrame url', () => { + const gppConsent = { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', applicableSections: [5, 6] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?gpp=DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN&gpp_sid=5%2C6&` + }]); + }); + + it('should return url without Gpp consent if gppConsent is undefined', () => { + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, undefined); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?` + }]); + }); + + it('should return iFrame url without Gpp consent if gppConsent.gppString is undefined', () => { + const gppConsent = { applicableSections: ['5'] }; + const result = spec.getUserSyncs({iframeEnabled: true}, undefined, undefined, undefined, gppConsent); + expect(result).to.deep.equal([{ + type: 'iframe', + url: `https://sync.connectad.io/iFrameSyncer?` + }]); + }); + }); + describe('getUserSyncs', () => { let testParams = [ { name: 'iframe/no gdpr or ccpa', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, null], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?'] + pixels: ['https://sync.connectad.io/iFrameSyncer?'] } }, { name: 'iframe/gdpr', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, {gdprApplies: true, consentString: '234234'}], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?gdpr=1&gdpr_consent=234234&'] } }, { @@ -494,15 +736,39 @@ describe('ConnectAd Adapter', function () { arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null, 'YN12'], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?us_privacy=YN12&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?us_privacy=YN12&'] } }, { name: 'iframe/ccpa & gdpr', - arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + arguments: [{ iframeEnabled: true, pixelEnabled: false }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + expect: { + type: 'iframe', + pixels: ['https://sync.connectad.io/iFrameSyncer?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + } + }, + { + name: 'image/ccpa & gdpr', + arguments: [{ iframeEnabled: false, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}, 'YN12'], + expect: { + type: 'image', + pixels: ['https://sync.connectad.io/ImageSyncer?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + } + }, + { + name: 'image/gdpr', + arguments: [{ iframeEnabled: false, pixelEnabled: true }, {}, {gdprApplies: true, consentString: '234234'}], + expect: { + type: 'image', + pixels: ['https://sync.connectad.io/ImageSyncer?gdpr=1&gdpr_consent=234234&'] + } + }, + { + name: 'should prioritize iframe over image for user sync', + arguments: [{ iframeEnabled: true, pixelEnabled: true }, {}, null], expect: { type: 'iframe', - pixels: ['https://cdn.connectad.io/connectmyusers.php?gdpr=1&gdpr_consent=234234&us_privacy=YN12&'] + pixels: ['https://sync.connectad.io/iFrameSyncer?'] } } ]; diff --git a/test/spec/modules/consentManagementGpp_spec.js b/test/spec/modules/consentManagementGpp_spec.js index 93a876d0233..25114ac6602 100644 --- a/test/spec/modules/consentManagementGpp_spec.js +++ b/test/spec/modules/consentManagementGpp_spec.js @@ -10,8 +10,6 @@ import {gppDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; import 'src/prebid.js'; -import {MODE_CALLBACK, MODE_MIXED} from '../../../libraries/cmp/cmpClient.js'; -import {GreedyPromise} from '../../../src/utils/promise.js'; let expect = require('chai').expect; @@ -141,170 +139,33 @@ describe('consentManagementGpp', function () { }); }); }); - describe('GPPClient.ping', () => { - function mkPingData(gppVersion) { - return { - gppVersion - } - } - Object.entries({ - 'unknown': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData(), - apiVersion: '1.1', - client({callback}) { - callback(this.pingData); - } - }, - '1.0': { - expectedMode: MODE_MIXED, - pingData: mkPingData('1.0'), - apiVersion: '1.0', - client() { - return this.pingData; - } - }, - '1.1 that runs callback immediately': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.1'), - apiVersion: '1.1', - client({callback}) { - callback(this.pingData); - } - }, - '1.1 that defers callback': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.1'), - apiVersion: '1.1', - client({callback}) { - setTimeout(() => callback(this.pingData), 10); - } - }, - '> 1.1': { - expectedMode: MODE_CALLBACK, - pingData: mkPingData('1.2'), - apiVersion: '1.1', - client({callback}) { - setTimeout(() => callback(this.pingData), 10); - } - } - }).forEach(([t, scenario]) => { - describe(`using CMP version ${t}`, () => { - let clients, mkClient; - beforeEach(() => { - clients = []; - mkClient = ({mode}) => { - const mockClient = function (args) { - if (args.command === 'ping') { - return Promise.resolve(scenario.client(args)); - } - } - mockClient.mode = mode; - mockClient.close = sinon.stub(); - clients.push(mockClient); - return mockClient; - } - }); - - it('should resolve to client with the correct mode', () => { - return GPPClient.ping(mkClient).then(([client]) => { - expect(client.cmp.mode).to.eql(scenario.expectedMode); - }); - }); - - it('should resolve to pingData', () => { - return GPPClient.ping(mkClient).then(([_, pingData]) => { - expect(pingData).to.eql(scenario.pingData); - }); - }); - it('should .close the probing client', () => { - return GPPClient.ping(mkClient).then(([client]) => { - sinon.assert.called(clients[0].close); - sinon.assert.notCalled(client.cmp.close); - }) - }); - - it('should .tag the client with version', () => { - return GPPClient.ping(mkClient).then(([client]) => { - expect(client.apiVersion).to.eql(scenario.apiVersion); - }) - }) - }) - }); - - it('should reject when mkClient returns null (CMP not found)', () => { - return GPPClient.ping(() => null).catch((err) => { - expect(err.message).to.match(/not found/); - }); - }); - - it('should reject when client rejects', () => { - const err = {some: 'prop'}; - const mockClient = () => Promise.reject(err); - mockClient.close = sinon.stub(); - return GPPClient.ping(() => mockClient).catch((result) => { - expect(result).to.eql(err); - sinon.assert.called(mockClient.close); - }); - }); - - it('should reject when callback is invoked with success = false', () => { - const err = 'error'; - const mockClient = ({callback}) => callback(err, false); - mockClient.close = sinon.stub(); - return GPPClient.ping(() => mockClient).catch((result) => { - expect(result).to.eql(err); - sinon.assert.called(mockClient.close); - }) - }) - }); - - describe('GPPClient.init', () => { - let makeCmp, cmpCalls, cmpResult; + describe('GPPClient.get', () => { + let makeCmp; beforeEach(() => { - cmpResult = {signalStatus: 'ready', gppString: 'mock-str'}; - cmpCalls = []; makeCmp = sinon.stub().callsFake(() => { - function mockCmp(args) { - cmpCalls.push(args); - return GreedyPromise.resolve(cmpResult); - } - mockCmp.close = sinon.stub(); - return mockCmp; + return sinon.stub() }); }); - it('should re-use same client', (done) => { - GPPClient.init(makeCmp).then(([client]) => { - GPPClient.init(makeCmp).then(([client2, consentPm]) => { - expect(client2).to.equal(client); - expect(cmpCalls.filter((el) => el.command === 'ping').length).to.equal(2) // recycled client should be refreshed - consentPm.then((consent) => { - expect(consent.gppString).to.eql('mock-str'); - done() - }) - }); - }); + it('should re-use same client', () => { + expect(GPPClient.get(makeCmp)).to.equal(GPPClient.get(makeCmp)); + sinon.assert.calledOnce(makeCmp); }); - it('should not re-use errors', (done) => { - cmpResult = GreedyPromise.reject(new Error()); - GPPClient.init(makeCmp).catch(() => { - cmpResult = {signalStatus: 'ready'}; - return GPPClient.init(makeCmp).then(([client]) => { - expect(client).to.exist; - done() - }) - }) + it('should not re-use errors', () => { + try { + GPPClient.get(sinon.stub().throws(new Error())); + } catch (e) {} + expect(GPPClient.get(makeCmp)).to.exist; }) }) describe('GPP client', () => { const CHANGE_EVENTS = ['sectionChange', 'signalStatus']; - let gppClient, gppData, cmpReady, eventListener; + let gppClient, gppData, eventListener; function mockClient(apiVersion = '1.1', cmpVersion = '1.1') { const mockCmp = sinon.stub().callsFake(function ({command, callback}) { @@ -314,10 +175,8 @@ describe('consentManagementGpp', function () { throw new Error('unexpected command: ' + command); } }) - const client = new GPPClient(cmpVersion, mockCmp); + const client = new GPPClient(mockCmp); client.apiVersion = apiVersion; - client.getGPPData = sinon.stub().callsFake(() => Promise.resolve(gppData)); - client.isCMPReady = sinon.stub().callsFake(() => cmpReady); client.events = CHANGE_EVENTS; return client; } @@ -325,7 +184,6 @@ describe('consentManagementGpp', function () { beforeEach(() => { gppDataHandler.reset(); eventListener = null; - cmpReady = true; gppData = { applicableSections: [7], gppString: 'mock-string', @@ -346,7 +204,7 @@ describe('consentManagementGpp', function () { describe('updateConsent', () => { it('should update data handler with consent data', () => { - return gppClient.updateConsent().then(data => { + return gppClient.updateConsent(gppData).then(data => { sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); expect(gppDataHandler.ready).to.be.true; @@ -358,8 +216,7 @@ describe('consentManagementGpp', function () { 'missing': null }).forEach(([t, data]) => { it(`should not update, and reject promise, when gpp data is ${t}`, (done) => { - gppData = data; - gppClient.updateConsent().catch(err => { + gppClient.updateConsent(data).catch(err => { expect(err.message).to.match(/empty/); expect(err.args).to.eql(data == null ? [] : [data]); expect(gppDataHandler.ready).to.be.false; @@ -368,15 +225,6 @@ describe('consentManagementGpp', function () { }); }) - it('should not update when gpp data rejects', (done) => { - gppData = Promise.reject(new Error('err')); - gppClient.updateConsent().catch(err => { - expect(gppDataHandler.ready).to.be.false; - expect(err.message).to.eql('err'); - done(); - }) - }); - describe('consent data validation', () => { Object.entries({ applicableSections: { @@ -394,7 +242,7 @@ describe('consentManagementGpp', function () { describe(t, () => { it('should not update', (done) => { Object.assign(gppData, {[prop]: value}); - gppClient.updateConsent().catch(err => { + gppClient.updateConsent(gppData).catch(err => { expect(err.message).to.match(/unexpected/); expect(err.args).to.eql([gppData]); expect(gppDataHandler.ready).to.be.false; @@ -409,23 +257,14 @@ describe('consentManagementGpp', function () { }); describe('init', () => { - beforeEach(() => { - gppClient.isCMPReady = function (pingData) { - return pingData.ready; - } - gppClient.getGPPData = function (pingData) { - return Promise.resolve(pingData); - } - }) - it('does not use initial pingData if CMP is not ready', () => { - gppClient.init({...gppData, ready: false}); + gppClient.init({...gppData, signalStatus: 'not ready'}); expect(eventListener).to.exist; expect(gppDataHandler.ready).to.be.false; }); it('uses initial pingData (and resolves promise) if CMP is ready', () => { - return gppClient.init({...gppData, ready: true}).then(data => { + return gppClient.init({...gppData, signalStatus: 'ready'}).then(data => { expect(eventListener).to.exist; sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); @@ -433,7 +272,7 @@ describe('consentManagementGpp', function () { }); it('rejects promise when CMP errors out', (done) => { - gppClient.init({ready: false}).catch((err) => { + gppClient.init({signalStatus: 'not ready'}).catch((err) => { expect(err.message).to.match(/error/); expect(err.args).to.eql(['error']) done(); @@ -447,7 +286,7 @@ describe('consentManagementGpp', function () { 'irrelevant': {eventName: 'irrelevant'} }).forEach(([t, evt]) => { it(`ignores ${t} events`, () => { - let pm = gppClient.init({ready: false}).catch((err) => err.args[0] !== 'done' && Promise.reject(err)); + let pm = gppClient.init({signalStatus: 'not ready'}).catch((err) => err.args[0] !== 'done' && Promise.reject(err)); eventListener(evt); eventListener('done', false); return pm; @@ -456,7 +295,7 @@ describe('consentManagementGpp', function () { it('rejects the promise when cmpStatus is "error"', (done) => { const evt = {eventName: 'other', pingData: {cmpStatus: 'error'}}; - gppClient.init({ready: false}).catch(err => { + gppClient.init({signalStatus: 'not ready'}).catch(err => { expect(err.message).to.match(/error/); expect(err.args).to.eql([evt]); done(); @@ -479,163 +318,36 @@ describe('consentManagementGpp', function () { }); it('does not fire consent data updates if the CMP is not ready', (done) => { - gppClient.init({ready: false}).catch(() => { + gppClient.init({signalStatus: 'not ready'}).catch(() => { expect(gppDataHandler.ready).to.be.false; done(); }); - eventListener({...gppData2, ready: false}); + eventListener({...gppData2, signalStatus: 'not ready'}); eventListener('done', false); }) it('fires consent data updates (and resolves promise) if CMP is ready', (done) => { - gppClient.init({ready: false}).then(data => { + gppClient.init({signalStatus: 'not ready'}).then(data => { sinon.assert.match(data, gppData2); done() }); - cmpReady = true; - eventListener(makeEvent({...gppData2, ready: true})); + eventListener(makeEvent({...gppData2, signalStatus: 'ready'})); }); it('keeps updating consent data on new events', () => { - let pm = gppClient.init({ready: false}).then(data => { + let pm = gppClient.init({signalStatus: 'not ready'}).then(data => { sinon.assert.match(data, gppData); sinon.assert.match(gppDataHandler.getConsentData(), gppData); }); - eventListener(makeEvent({...gppData, ready: true})); + eventListener(makeEvent({...gppData, signalStatus: 'ready'})); return pm.then(() => { - eventListener(makeEvent({...gppData2, ready: true})) + eventListener(makeEvent({...gppData2, signalStatus: 'ready'})) }).then(() => { sinon.assert.match(gppDataHandler.getConsentData(), gppData2); }); }); - }) - }) - }); - }); - - describe('GPP 1.0 protocol', () => { - let mockCmp, gppClient; - beforeEach(() => { - mockCmp = sinon.stub(); - gppClient = new (GPPClient.getClient('1.0'))('1.0', mockCmp); - }); - - describe('isCMPReady', () => { - Object.entries({ - 'loaded': [true, 'loaded'], - 'other': [false, 'other'], - 'undefined': [false, undefined] - }).forEach(([t, [expected, cmpStatus]]) => { - it(`should be ${expected} when cmpStatus is ${t}`, () => { - expect(gppClient.isCMPReady(Object.assign({}, {cmpStatus}))).to.equal(expected); - }); - }); - }); - - describe('getGPPData', () => { - let gppData, pingData; - beforeEach(() => { - gppData = { - gppString: 'mock-string', - supportedAPIs: ['usnat'], - applicableSections: [7, 8] - } - pingData = { - supportedAPIs: gppData.supportedAPIs - }; - }); - - function mockCmpCommands(commands) { - mockCmp.callsFake(({command, parameter}) => { - if (commands.hasOwnProperty((command))) { - return Promise.resolve(commands[command](parameter)); - } else { - return Promise.reject(new Error(`unrecognized command ${command}`)) - } - }) - } - - it('should retrieve consent string and applicableSections', () => { - mockCmpCommands({ - getGPPData: () => gppData - }) - return gppClient.getGPPData(pingData).then(data => { - sinon.assert.match(data, gppData); - }) - }); - - it('should reject when getGPPData rejects', (done) => { - mockCmpCommands({ - getGPPData: () => Promise.reject(new Error('err')) - }); - gppClient.getGPPData(pingData).catch(err => { - expect(err.message).to.eql('err'); - done(); }); }); - - it('should not choke if supportedAPIs is missing', () => { - [gppData, pingData].forEach(ob => { delete ob.supportedAPIs; }) - mockCmpCommands({ - getGPPData: () => gppData - }); - return gppClient.getGPPData(pingData).then(res => { - expect(res.gppString).to.eql(gppData.gppString); - expect(res.parsedSections).to.eql({}); - }) - }) - - describe('section data', () => { - let usnat, parsedUsnat; - - function mockSections(sections) { - mockCmpCommands({ - getGPPData: () => gppData, - getSection: (api) => (sections[api]) - }); - }; - - beforeEach(() => { - usnat = { - MockField: 'val', - OtherField: 'o', - Gpc: true - }; - parsedUsnat = [ - { - MockField: 'val', - OtherField: 'o' - }, - { - SubsectionType: 1, - Gpc: true - } - ] - }); - - it('retrieves section data', () => { - mockSections({usnat}); - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}) - }); - }); - - it('does not choke if a section is missing', () => { - mockSections({usnat}); - gppData.supportedAPIs = ['usnat', 'missing']; - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}); - }) - }); - - it('does not choke if a section fails', () => { - mockSections({usnat, err: Promise.reject(new Error('err'))}); - gppData.supportedAPIs = ['usnat', 'err']; - return gppClient.getGPPData(pingData).then(data => { - expect(data.parsedSections).to.eql({usnat: parsedUsnat}); - }) - }); - }) }); }); @@ -643,7 +355,7 @@ describe('consentManagementGpp', function () { let mockCmp, gppClient; beforeEach(() => { mockCmp = sinon.stub(); - gppClient = new (GPPClient.getClient('1.1'))('1.1', mockCmp); + gppClient = new GPPClient(mockCmp); }); describe('isCMPReady', () => { @@ -657,82 +369,6 @@ describe('consentManagementGpp', function () { }); }); }); - - it('gets GPPData from pingData', () => { - mockCmp.throws(new Error()); - const pingData = { - 'gppVersion': '1.1', - 'cmpStatus': 'loaded', - 'cmpDisplayStatus': 'disabled', - 'supportedAPIs': [ - '5:tcfcav1', - '7:usnat', - '8:usca', - '9:usva', - '10:usco', - '11:usut', - '12:usct' - ], - 'signalStatus': 'ready', - 'cmpId': 31, - 'sectionList': [ - 7 - ], - 'applicableSections': [ - 7 - ], - 'gppString': 'DBABL~BAAAAAAAAgA.QA', - 'parsedSections': { - 'usnat': [ - { - 'Version': 1, - 'SharingNotice': 0, - 'SaleOptOutNotice': 0, - 'SharingOptOutNotice': 0, - 'TargetedAdvertisingOptOutNotice': 0, - 'SensitiveDataProcessingOptOutNotice': 0, - 'SensitiveDataLimitUseNotice': 0, - 'SaleOptOut': 0, - 'SharingOptOut': 0, - 'TargetedAdvertisingOptOut': 0, - 'SensitiveDataProcessing': [ - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0 - ], - 'KnownChildSensitiveDataConsents': [ - 0, - 0 - ], - 'PersonalDataConsents': 0, - 'MspaCoveredTransaction': 2, - 'MspaOptOutOptionMode': 0, - 'MspaServiceProviderMode': 0 - }, - { - 'SubsectionType': 1, - 'Gpc': false - } - ] - } - }; - return gppClient.getGPPData(pingData).then((gppData) => { - sinon.assert.match(gppData, { - gppString: pingData.gppString, - applicableSections: pingData.applicableSections, - parsedSections: pingData.parsedSections - }) - }) - }) }) describe('requestBidsHook tests:', function () { @@ -858,56 +494,76 @@ describe('consentManagementGpp', function () { }); }); - describe('already known consentData:', function () { - let cmpStub = sinon.stub(); - - function mockCMP(pingData) { - return function (command, callback) { + describe('on CMP sectionChange events', () => { + let pingData, triggerCMPEvent; + beforeEach(() => { + pingData = { + applicableSections: [7], + gppString: 'xyz', + }; + triggerCMPEvent = null; + window.__gpp = sinon.stub().callsFake(function (command, callback) { switch (command) { case 'addEventListener': // eslint-disable-next-line standard/no-callback-literal - callback({eventName: 'sectionChange', pingData}) + triggerCMPEvent = (event, payload = {}) => callback({eventName: event, pingData: {...pingData, ...payload}}) break; case 'ping': callback(pingData) break; + default: + throw new Error('unexpected __gpp invocation') } - } - } - - beforeEach(function () { - didHookReturn = false; - window.__gpp = function () {}; + }); + setConsentConfig(goodConfig); }); - afterEach(function () { - config.resetConfig(); - cmpStub.restore(); + afterEach(() => { delete window.__gpp; resetConsentData(); }); - it('should bypass CMP and simply use previously stored consentData', function () { - let testConsentData = { - applicableSections: [7], - gppString: 'xyz', - }; - - cmpStub = sinon.stub(window, '__gpp').callsFake(mockCMP({...testConsentData, signalStatus: 'ready'})); - setConsentConfig(goodConfig); - requestBidsHook(() => {}, {}); - cmpStub.reset(); - + function startHook() { + let hookRan = false; requestBidsHook(() => { - didHookReturn = true; + hookRan = true; }, {}); - let consent = gppDataHandler.getConsentData(); + return () => new Promise((resolve) => setTimeout(resolve(hookRan), 5)); + } - expect(didHookReturn).to.be.true; - expect(consent.gppString).to.equal(testConsentData.gppString); - expect(consent.applicableSections).to.deep.equal(testConsentData.applicableSections); - sinon.assert.notCalled(cmpStub); + it('should wait for signalStatus: ready', async () => { + const didHookRun = startHook(); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'not ready'}); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + expect(await didHookRun()).to.be.true; + expect(gppDataHandler.getConsentData().gppString).to.eql('xyz'); + }); + + it('should re-use GPP data once ready', async () => { + let didHookRun = startHook(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await didHookRun(); + window.__gpp.reset(); + didHookRun = startHook(); + expect(await didHookRun()).to.be.true; + sinon.assert.notCalled(window.__gpp); + }); + + it('after signalStatus: ready, should wait again for signalStatus: ready', async () => { + let didHookRun = startHook(); + triggerCMPEvent('sectionChange', {signalStatus: 'ready'}); + await didHookRun(); + for (let run of ['first', 'second']) { + triggerCMPEvent('cmpDisplayStatus', {signalStatus: 'not ready'}); + didHookRun = startHook(); + expect(await didHookRun()).to.be.false; + triggerCMPEvent('sectionChange', {signalStatus: 'ready', gppString: run}); + expect(await didHookRun()).to.be.true; + expect(gppDataHandler.getConsentData().gppString).to.eql(run); + } }); - }); + }) }); }); diff --git a/test/spec/modules/consentManagementUsp_spec.js b/test/spec/modules/consentManagementUsp_spec.js index c372c66f7f0..5f589ee5fe7 100644 --- a/test/spec/modules/consentManagementUsp_spec.js +++ b/test/spec/modules/consentManagementUsp_spec.js @@ -493,7 +493,6 @@ describe('consentManagement', function () { sinon.assert.notCalled(utils.logWarn); sinon.assert.notCalled(utils.logError); - expect(consentMeta.usp).to.equal(testConsentData.uspString); expect(consentMeta.generatedAt).to.be.above(1644367751709); }); diff --git a/test/spec/modules/consentManagement_spec.js b/test/spec/modules/consentManagement_spec.js index c1ed042a2c8..9131e6748e8 100644 --- a/test/spec/modules/consentManagement_spec.js +++ b/test/spec/modules/consentManagement_spec.js @@ -8,7 +8,7 @@ import { setConsentConfig, staticConsentData, userCMP -} from 'modules/consentManagement.js'; +} from 'modules/consentManagementTcf.js'; import {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import {config} from 'src/config.js'; @@ -284,7 +284,7 @@ describe('consentManagement', function () { expect(consent).to.be.null; }); - it('should call gpdrDataHandler.setConsentData() when unknown CMP api is used', () => { + it('should call gdprDataHandler.setConsentData() when unknown CMP api is used', () => { setConsentConfig({gdpr: {cmpApi: 'invalid'}}); let hookRan = false; requestBidsHook(() => { hookRan = true; }, {}); diff --git a/test/spec/modules/consumableBidAdapter_spec.js b/test/spec/modules/consumableBidAdapter_spec.js index d8e75454245..073e889d172 100644 --- a/test/spec/modules/consumableBidAdapter_spec.js +++ b/test/spec/modules/consumableBidAdapter_spec.js @@ -66,6 +66,11 @@ const BIDDER_REQUEST_1 = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -130,6 +135,11 @@ const BIDDER_REQUEST_2 = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -177,6 +187,11 @@ const BIDDER_REQUEST_VIDEO = { 'http://example.com/iframe1.html', 'http://example.com/iframe2.html' ] + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -188,6 +203,11 @@ const BIDDER_REQUEST_EMPTY = { gdprConsent: { consentString: 'consent-test', gdprApplies: false + }, + ortb2: { + device: { + language: 'en' + } } }; @@ -519,6 +539,12 @@ describe('Consumable BidAdapter', function () { expect(data1.placements[0].bidfloor).to.equal(0.05); expect(data2.placements[0].bidfloor).to.equal(0.15); }); + it('should contain the language param', function () { + let request = spec.buildRequests(BIDDER_REQUEST_1.bidRequest, BIDDER_REQUEST_1); + let data = JSON.parse(request.data); + + expect(data.lang).to.equal('en'); + }); }); describe('interpretResponse validation', function () { it('response should have valid bidderCode', function () { diff --git a/test/spec/modules/contentexchangeBidAdapter_spec.js b/test/spec/modules/contentexchangeBidAdapter_spec.js index 1b3dc4f19c9..913c9072dd5 100644 --- a/test/spec/modules/contentexchangeBidAdapter_spec.js +++ b/test/spec/modules/contentexchangeBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/contentexchangeBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'contentexchange' +const bidder = 'contentexchange'; describe('ContentexchangeBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,9 +26,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: BANNER - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -31,9 +41,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: VIDEO - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,9 +65,9 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - placementId: 'test', - adFormat: NATIVE - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -70,15 +80,26 @@ describe('ContentexchangeBidAdapter', function () { } }, params: { - adFormat: BANNER + } } const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -115,6 +136,7 @@ describe('ContentexchangeBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -132,7 +154,7 @@ describe('ContentexchangeBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -142,11 +164,62 @@ describe('ContentexchangeBidAdapter', 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.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); expect(placement.bidId).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -172,8 +245,10 @@ describe('ContentexchangeBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +263,38 @@ describe('ContentexchangeBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -372,6 +473,7 @@ describe('ContentexchangeBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -396,5 +498,17 @@ describe('ContentexchangeBidAdapter', function () { 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') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/contxtfulBidAdapter_spec.js b/test/spec/modules/contxtfulBidAdapter_spec.js new file mode 100644 index 00000000000..14b4b94f062 --- /dev/null +++ b/test/spec/modules/contxtfulBidAdapter_spec.js @@ -0,0 +1,484 @@ +import { spec } from 'modules/contxtfulBidAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; +import * as ajax from 'src/ajax.js'; +const VERSION = 'v1'; +const CUSTOMER = 'CUSTOMER'; +const BIDDER_ENDPOINT = 'prebid.receptivity.io'; +const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' }; + +describe('contxtful bid adapter', function () { + const adapter = newBidder(spec); + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + + describe('is a functions', function () { + it('exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('valid code', function () { + it('should return the bidder code of contxtful', function () { + expect(spec.code).to.eql('contxtful'); + }); + }); + + let bidRequests = + [ + { + bidder: 'contxtful', + bidId: 'bId1', + custom_param_1: 'value_1', + transactionId: 'tId1', + params: { + bcat: ['cat1', 'cat2'], + badv: ['adv1', 'adv2'], + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + ortb2Imp: { + ext: { + tid: 't-id-test-1', + gpid: 'gpid-id-unitest-1' + }, + }, + schain: { + ver: '1.0', + complete: 1, + nodes: [ + { + asi: 'schain-seller-1.com', + sid: '00001', + hp: 1, + }, + ], + }, + getFloor: () => ({ currency: 'CAD', floor: 10 }), + } + ]; + + let expectedReceptivityData = { + rx: RX_FROM_API, + params: { + ev: VERSION, + ci: CUSTOMER, + }, + }; + + let bidderRequest = { + refererInfo: { + ref: 'https://my-referer-custom.com', + }, + ortb2: { + source: { + tid: 'auction-id', + }, + property_1: 'string_val_1', + regs: { + coppa: 1, + ext: { + us_privacy: '12345' + } + }, + user: { + data: [ + { + name: 'contxtful', + ext: expectedReceptivityData + } + ], + ext: { + eids: [ + { + source: 'id5-sync.com', + uids: [ + { + atype: 1, + id: 'fake-id5id', + }, + ] + } + ] + } + } + + }, + timeout: 1234, + uspConsent: '12345' + }; + + describe('valid configuration', function() { + const theories = [ + [ + null, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'null object for config', + ], + [ + {}, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty object for config', + ], + [ + { customer: CUSTOMER }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'customer only in config', + ], + [ + { version: VERSION }, + 'contxfulBidAdapter: contxtful.customer should be a non-empty string', + 'version only in config', + ], + [ + { customer: CUSTOMER, version: '' }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty string for version', + ], + [ + { customer: '', version: VERSION }, + 'contxfulBidAdapter: contxtful.customer should be a non-empty string', + 'empty string for customer', + ], + [ + { customer: '', version: '' }, + 'contxfulBidAdapter: contxtful.version should be a non-empty string', + 'empty string for version & customer', + ], + ]; + + theories.forEach(([params, expectedErrorMessage, description]) => { + it('detects invalid configuration and throws the expected error (' + description + ')', () => { + config.setConfig({ + contxtful: params + }); + expect(() => spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + })).to.throw( + expectedErrorMessage + ); + }); + }); + + it('uses a valid configuration and returns the right url', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION} + }); + const bidRequest = spec.buildRequests(bidRequests); + expect(bidRequest.url).to.eq('https://' + BIDDER_ENDPOINT + `/${VERSION}/prebid/${CUSTOMER}/bid`) + }); + + it('will take specific ortb2 configuration parameters and returns it in ortb2 object', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + expect(bidRequest.data.ortb2.property_1).to.equal('string_val_1'); + }); + }); + + describe('valid bid request', function () { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('will return a data property containing properties ortb2, bidRequests, bidderRequest and config', () => { + expect(bidRequest.data).not.to.be.undefined; + expect(bidRequest.data.ortb2).not.to.be.undefined; + expect(bidRequest.data.bidRequests).not.to.be.undefined; + expect(bidRequest.data.bidderRequest).not.to.be.undefined; + expect(bidRequest.data.config).not.to.be.undefined; + }); + + it('will take custom parameters in the bid request and within the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].custom_param_1).to.equal('value_1') + }); + + it('will return any supply chain parameters within the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].schain.ver).to.equal('1.0'); + }); + + it('will return floor request within the bidFloor parameter in the bidRequests array', () => { + expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('CAD'); + expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(10); + }); + + it('will return the usp string in the uspConsent parameter within the bidderRequest property', () => { + expect(bidRequest.data.bidderRequest.uspConsent).to.equal('12345'); + }); + + it('will contains impressions array on ortb2.imp object for all ad units', () => { + expect(bidRequest.data.ortb2.imp.length).to.equal(1); + expect(bidRequest.data.ortb2.imp[0].id).to.equal('bId1'); + }); + + it('will contains the registration on ortb2.regs object', () => { + expect(bidRequest.data.ortb2.regs).not.to.be.undefined; + expect(bidRequest.data.ortb2.regs.coppa).to.equal(1); + expect(bidRequest.data.ortb2.regs.ext.us_privacy).to.equal('12345') + }) + + it('will contains the eids modules within the ortb2.user.ext.eids', () => { + expect(bidRequest.data.ortb2.user.ext.eids).not.to.be.undefined; + expect(bidRequest.data.ortb2.user.ext.eids[0].source).to.equal('id5-sync.com'); + expect(bidRequest.data.ortb2.user.ext.eids[0].uids[0].id).to.equal('fake-id5id'); + }); + + it('will contains the receptivity value within the ortb2.user.data with contxtful name', () => { + let obtained_receptivity_data = bidRequest.data.ortb2.user.data.filter(function(userData) { + return userData.name == 'contxtful'; + }); + expect(obtained_receptivity_data.length).to.equal(1); + expect(obtained_receptivity_data[0].ext).to.deep.equal(expectedReceptivityData); + }); + + it('will contains ortb2Imp of the bid request within the ortb2.imp.ext', () => { + let first_imp = bidRequest.data.ortb2.imp[0]; + expect(first_imp.ext).not.to.be.undefined; + expect(first_imp.ext.tid).to.equal('t-id-test-1'); + expect(first_imp.ext.gpid).to.equal('gpid-id-unitest-1'); + }); + }); + + describe('valid bid request with no floor module', () => { + let noFloorsBidRequests = + [ + { + bidder: 'contxtful', + bidId: 'bId1', + transactionId: 'tId1', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + }, + { + bidder: 'contxtful', + bidId: 'bId2', + transactionId: 'tId2', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600] + ] + }, + }, + params: { + bidfloor: 54 + } + }, + ]; + + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const bidRequest = spec.buildRequests(noFloorsBidRequests, bidderRequest); + it('will contains default value of floor if the bid request do not contains floor function', () => { + expect(bidRequest.data.bidRequests[0].bidFloor.currency).to.equal('USD'); + expect(bidRequest.data.bidRequests[0].bidFloor.floor).to.equal(0); + }); + + it('will take the param.bidfloor as floor value if possible', () => { + expect(bidRequest.data.bidRequests[1].bidFloor.currency).to.equal('USD'); + expect(bidRequest.data.bidRequests[1].bidFloor.floor).to.equal(54); + }); + }); + + describe('valid bid response', () => { + const bidResponse = [ + { + 'requestId': 'arequestId', + 'originalCpm': 1.5, + 'cpm': 1.35, + 'currency': 'CAD', + 'width': 300, + 'height': 600, + 'creativeId': 'creativeid', + 'netRevenue': true, + 'ttl': 300, + 'ad': '', + 'mediaType': 'banner', + 'syncs': [ + { + 'url': 'mysyncurl.com?qparam1=qparamv1&qparam2=qparamv2' + } + ] + } + ]; + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + + it('will interpret response correcly', () => { + const bids = spec.interpretResponse({ body: bidResponse }, bidRequest); + expect(bids).not.to.be.undefined; + expect(bids).to.have.lengthOf(1); + expect(bids).to.deep.equal(bidResponse); + }); + + it('will return empty response if bid response is empty', () => { + const bids = spec.interpretResponse({ body: [] }, bidRequest); + expect(bids).to.have.lengthOf(0); + }) + + it('will trigger user sync if enable pixel mode', () => { + const syncOptions = { + pixelEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'image' + } + ]); + }); + + it('will trigger user sync if enable iframe mode', () => { + const syncOptions = { + iframeEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/iframe?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'iframe' + } + ]); + }); + + describe('no sync option', () => { + it('will return image sync if no sync options', () => { + const userSyncs = spec.getUserSyncs({}, [{ body: bidResponse }]); + expect(userSyncs).to.deep.equal([ + { + 'url': 'mysyncurl.com/image?pbjs=1&coppa=0&qparam1=qparamv1&qparam2=qparamv2', + 'type': 'image' + } + ]); + }); + it('will return empty value if no server response', () => { + const userSyncs = spec.getUserSyncs({}, []); + expect(userSyncs).to.have.lengthOf(0); + const userSyncs2 = spec.getUserSyncs({}, null); + expect(userSyncs2).to.have.lengthOf(0); + }); + }); + + it('will return empty value if no server response', () => { + const syncOptions = { + iframeEnabled: true + }; + + const userSyncs = spec.getUserSyncs(syncOptions, []); + expect(userSyncs).to.have.lengthOf(0); + const userSyncs2 = spec.getUserSyncs(syncOptions, null); + expect(userSyncs2).to.have.lengthOf(0); + }); + + describe('onTimeout callback', () => { + it('will always call server with sendBeacon available', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(true); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw; + expect(beaconStub.called).to.be.true; + expect(ajaxStub.called).to.be.false; + }); + + it('will always call server with sendBeacon not available', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + expect(spec.onTimeout({'customData': 'customvalue'})).to.not.throw; + expect(beaconStub.called).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + expect(ajaxStub.calledOnce).to.be.true; + }); + }); + + describe('on onBidderError callback', () => { + it('will always call server', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidderError({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onBidWon callback', () => { + it('will always call server', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION}, + }); + + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidWon({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onBidBillable callback', () => { + it('will always call server when sampling rate is configured to be 1.0', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, sampling: {onBidBillable: 1.0}}, + }); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onBidBillable({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + + describe('on onAdRenderSucceeded callback', () => { + it('will always call server when sampling rate is configured to be 1.0', () => { + config.setConfig({ + contxtful: {customer: CUSTOMER, version: VERSION, sampling: {onAdRenderSucceeded: 1.0}}, + }); + const ajaxStub = sandbox.stub(ajax, 'ajax'); + const beaconStub = sandbox.stub(ajax, 'sendBeacon').returns(false); + spec.onAdRenderSucceeded({'customData': 'customvalue'}); + expect(ajaxStub.calledOnce).to.be.true; + expect(beaconStub.returned(false)).to.be.true; + }); + }); + }); +}); diff --git a/test/spec/modules/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index 541c0e6e6dd..e31ef554da0 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -1,25 +1,38 @@ -import { contxtfulSubmodule } from '../../../modules/contxtfulRtdProvider.js'; +import { contxtfulSubmodule, extractParameters } from '../../../modules/contxtfulRtdProvider.js'; import { expect } from 'chai'; import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; - +import { getStorageManager } from '../../../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../../../src/activities/modules.js'; import * as events from '../../../src/events'; +import Sinon from 'sinon'; + +const MODULE_NAME = 'contxtful'; -const _ = null; const VERSION = 'v1'; const CUSTOMER = 'CUSTOMER'; -const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/p.js`; -const INITIAL_RECEPTIVITY = { ReceptivityState: 'INITIAL_RECEPTIVITY' }; -const INITIAL_RECEPTIVITY_EVENT = new CustomEvent('initialReceptivity', { detail: INITIAL_RECEPTIVITY }); +const CONTXTFUL_CONNECTOR_ENDPOINT = `https://api.receptivity.io/${VERSION}/prebid/${CUSTOMER}/connector/rxConnector.js`; + +const RX_FROM_SESSION_STORAGE = { ReceptivityState: 'Receptive', test_info: 'rx_from_session_storage' }; +const RX_FROM_API = { ReceptivityState: 'Receptive', test_info: 'rx_from_engine' }; -const CONTXTFUL_API = { GetReceptivity: sinon.stub() } -const RX_ENGINE_IS_READY_EVENT = new CustomEvent('rxEngineIsReady', {detail: CONTXTFUL_API}); +const RX_API_MOCK = { receptivity: sinon.stub(), receptivityBatched: sinon.stub() }; +const RX_CONNECTOR_MOCK = { + fetchConfig: sinon.stub(), + rxApiBuilder: sinon.stub(), +}; + +const TIMEOUT = 10; +const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: {[CUSTOMER]: RX_CONNECTOR_MOCK}, bubbles: true }); function buildInitConfig(version, customer) { return { name: 'contxtful', params: { - version, - customer, + version: version, + customer: customer, + hostname: 'api.receptivity.io', + bidders: ['mock-bidder-code'], + adServerTargeting: true, }, }; } @@ -29,13 +42,28 @@ describe('contxtfulRtdProvider', function () { let loadExternalScriptTag; let eventsEmitSpy; + const storage = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: MODULE_NAME }); + beforeEach(() => { loadExternalScriptTag = document.createElement('script'); loadExternalScriptStub.callsFake((_url, _moduleName) => loadExternalScriptTag); - CONTXTFUL_API.GetReceptivity.reset(); + RX_API_MOCK.receptivity.reset(); + RX_API_MOCK.receptivity.callsFake(() => RX_FROM_API); + + RX_API_MOCK.receptivityBatched.reset(); + RX_API_MOCK.receptivityBatched.callsFake((bidders) => bidders.reduce((accumulator, bidder) => { accumulator[bidder] = RX_FROM_API; return accumulator; }, {})); + + RX_CONNECTOR_MOCK.fetchConfig.reset(); + RX_CONNECTOR_MOCK.fetchConfig.callsFake((tagId) => new Promise((resolve, reject) => resolve({ tag_id: tagId }))); + + RX_CONNECTOR_MOCK.rxApiBuilder.reset(); + RX_CONNECTOR_MOCK.rxApiBuilder.callsFake((_config) => new Promise((resolve, reject) => resolve(RX_API_MOCK))); eventsEmitSpy = sandbox.spy(events, ['emit']); + + let tagId = CUSTOMER; + sessionStorage.clear(); }); afterEach(function () { @@ -43,7 +71,7 @@ describe('contxtfulRtdProvider', function () { sandbox.restore(); }); - describe('extractParameters with invalid configuration', () => { + describe('extractParameters', () => { const { params: { customer, version }, } = buildInitConfig(VERSION, CUSTOMER); @@ -87,27 +115,27 @@ describe('contxtfulRtdProvider', function () { theories.forEach(([params, expectedErrorMessage, _description]) => { const config = { name: 'contxtful', params }; - it('throws the expected error', () => { - expect(() => contxtfulSubmodule.extractParameters(config)).to.throw( + it('detects invalid configuration and throws the expected error', () => { + expect(() => extractParameters(config)).to.throw( expectedErrorMessage ); }); }); }); - describe('initialization with invalid config', function () { - it('returns false', () => { + describe('extractParameters', function () { + it('detects invalid configuration and returns false', () => { expect(contxtfulSubmodule.init({})).to.be.false; }); }); - describe('initialization with valid config', function () { - it('returns true when initializing', () => { + describe('init', function () { + it('uses a valid configuration and returns true when initializing', () => { const config = buildInitConfig(VERSION, CUSTOMER); expect(contxtfulSubmodule.init(config)).to.be.true; }); - it('loads contxtful module script asynchronously', (done) => { + it('loads a RX connector script asynchronously', (done) => { contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); setTimeout(() => { @@ -115,54 +143,84 @@ describe('contxtfulRtdProvider', function () { expect(loadExternalScriptStub.args[0][0]).to.equal( CONTXTFUL_CONNECTOR_ENDPOINT ); + done(); - }, 10); + }, TIMEOUT); }); }); - describe('load external script return falsy', function () { + describe('init', function () { it('returns true when initializing', () => { - loadExternalScriptStub.callsFake(() => {}); + loadExternalScriptStub.callsFake((url, moduleCode, callback, doc, attributes) => { + return { addEventListener: (type, listener) => { } }; + }); const config = buildInitConfig(VERSION, CUSTOMER); expect(contxtfulSubmodule.init(config)).to.be.true; }); }); - describe('rxEngine from external script', function () { - it('use rxEngine api to get receptivity', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); - loadExternalScriptTag.dispatchEvent(RX_ENGINE_IS_READY_EVENT); + describe('init', function () { + it('uses the RX API to get receptivity', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(RX_API_MOCK.receptivity.callCount, 'receptivity 42').to.be.equal(1); + expect(RX_API_MOCK.receptivity.firstCall.returnValue, 'receptivity').to.be.equal(RX_FROM_API); + done(); + }, TIMEOUT); + }); + }); - contxtfulSubmodule.getTargetingData(['ad-slot']); + describe('init', function () { + it('gets the RX API returned by an external script', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); - expect(CONTXTFUL_API.GetReceptivity.calledOnce).to.be.true; + setTimeout(() => { + contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount, 'fetchConfig').at.least(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount, 'rxApiBuilder').at.least(1); + done(); + }, TIMEOUT); }); }); - describe('initial receptivity is not dispatched', function () { - it('does not initialize receptivity value', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + describe('init', function () { + it('detect that initial receptivity is not dispatched and it does not initialize receptivity value', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); - let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); - expect(targetingData).to.deep.equal({}); + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(targetingData).to.deep.equal({}); + done(); + }, TIMEOUT); }); }); - describe('initial receptivity is invalid', function () { + describe('init', function () { const theories = [ [new Event('initialReceptivity'), 'event without details'], - [new CustomEvent('initialReceptivity', { }), 'custom event without details'], + [new CustomEvent('initialReceptivity', {}), 'custom event without details'], [new CustomEvent('initialReceptivity', { detail: {} }), 'custom event with invalid details'], [new CustomEvent('initialReceptivity', { detail: { ReceptivityState: '' } }), 'custom event with details without ReceptivityState'], ]; theories.forEach(([initialReceptivityEvent, _description]) => { - it('does not initialize receptivity value', () => { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + it('figures out that initial receptivity is invalid and it does not initialize receptivity value', (done) => { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); loadExternalScriptTag.dispatchEvent(initialReceptivityEvent); - let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot']); - expect(targetingData).to.deep.equal({}); + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(['ad-slot'], config); + expect(targetingData).to.deep.equal({}); + done(); + }, TIMEOUT); }); }) }); @@ -173,28 +231,426 @@ describe('contxtfulRtdProvider', function () { [[], {}, 'empty ad-slots'], [ ['ad-slot'], - { 'ad-slot': { ReceptivityState: 'INITIAL_RECEPTIVITY' } }, + { 'ad-slot': RX_FROM_API }, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + 'ad-slot-1': RX_FROM_API, + 'ad-slot-2': RX_FROM_API, + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, description]) => { + it('adds receptivity to the ad units using the RX API', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData, description).to.deep.equal(expected, description); + done(); + }, TIMEOUT); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + {}, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, description]) => { + it('honours "adServerTargeting" and the RX API is not called', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + config.params.adServerTargeting = false; + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let _ = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(RX_API_MOCK.receptivity.callCount).to.be.equal(0); + done(); + }, TIMEOUT); + }); + + it('honours adServerTargeting and it does not add receptivity to the ad units', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + config.params.adServerTargeting = false; + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + setTimeout(() => { + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData, description).to.deep.equal(expected); + done(); + }, TIMEOUT); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + { 'ad-slot': RX_FROM_SESSION_STORAGE }, 'single ad-slot', ], [ ['ad-slot-1', 'ad-slot-2'], { - 'ad-slot-1': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, - 'ad-slot-2': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, + 'ad-slot-1': RX_FROM_SESSION_STORAGE, + 'ad-slot-2': RX_FROM_SESSION_STORAGE, }, 'many ad-slots', ], ]; theories.forEach(([adUnits, expected, _description]) => { - it('adds "ReceptivityState" to the adUnits', function () { - contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); - loadExternalScriptTag.dispatchEvent(INITIAL_RECEPTIVITY_EVENT); + it('uses non-expired info from session storage and adds receptivity to the ad units using session storage', function (done) { + // Simulate that there was a write to sessionStorage in the past. + storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE})) - expect(contxtfulSubmodule.getTargetingData(adUnits)).to.deep.equal( - expected - ); + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData).to.deep.equal(expected); + + done(); + }); + }); + }); + + describe('getTargetingData', function () { + const theories = [ + [undefined, {}, 'undefined ad-slots'], + [[], {}, 'empty ad-slots'], + [ + ['ad-slot'], + {}, + 'single ad-slot', + ], + [ + ['ad-slot-1', 'ad-slot-2'], + { + }, + 'many ad-slots', + ], + ]; + + theories.forEach(([adUnits, expected, _description]) => { + it('ignores expired info from session storage and does not forward the info to ad units', function (done) { + // Simulate that there was a write to sessionStorage in the past. + storage.setDataInSessionStorage(CUSTOMER, JSON.stringify({exp: new Date().getTime() - 100, rx: RX_FROM_SESSION_STORAGE})); + + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + + let targetingData = contxtfulSubmodule.getTargetingData(adUnits, config); + expect(targetingData).to.deep.equal(expected); + + done(); }); }); }); + + describe('getBidRequestData', function () { + it('calls once the onDone callback', function (done) { + contxtfulSubmodule.init(buildInitConfig(VERSION, CUSTOMER)); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, buildInitConfig(VERSION, CUSTOMER)); + expect(onDoneSpy.calledOnce).to.be.true; + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('does not write receptivity to the global OpenRTB 2 fragment', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDone = () => 42; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDone, config); + expect(reqBidsConfigObj.ortb2Fragments.global).to.deep.equal({}); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('writes receptivity to the configured bidder OpenRTB 2 fragments', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let expectedData = { + name: 'contxtful', + ext: { + rx: RX_FROM_API, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let data = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + + expect(data.name).to.deep.equal(expectedData.name); + expect(data.ext.rx).to.deep.equal(expectedData.ext.rx); + expect(data.ext.params).to.deep.equal(expectedData.ext.params); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('uses non-expired info from session storage and adds receptivity to the reqBidsConfigObj', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + + // Simulate that there was a write to sessionStorage in the past. + let bidder = config.params.bidders[0]; + + storage.setDataInSessionStorage(`${config.params.customer}_${bidder}`, JSON.stringify({exp: new Date().getTime() + 1000, rx: RX_FROM_SESSION_STORAGE})); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + contxtfulSubmodule.init(config); + + // Since the RX_CONNECTOR_IS_READY_EVENT event was not dispatched, the RX engine is not loaded. + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, () => {}, config); + + setTimeout(() => { + let ortb2BidderFragment = reqBidsConfigObj.ortb2Fragments.bidder[bidder]; + let userData = ortb2BidderFragment.user.data; + let contxtfulData = userData[0]; + + expect(contxtfulData.name).to.be.equal('contxtful'); + expect(contxtfulData.ext.rx).to.deep.equal(RX_FROM_SESSION_STORAGE); + expect(contxtfulData.ext.params).to.deep.equal({ + ev: config.params.version, + ci: config.params.customer, + }); + + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('uses the RX API', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + expect(RX_CONNECTOR_MOCK.fetchConfig.callCount).at.least(1); + expect(RX_CONNECTOR_MOCK.rxApiBuilder.callCount).at.least(1); + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + expect(onDoneSpy.callCount).to.equal(1); + expect(RX_API_MOCK.receptivityBatched.callCount).to.equal(1); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + it('adds receptivity to the reqBidsConfigObj', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let expectedData = { + name: 'contxtful', + ext: { + rx: RX_FROM_API, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let data = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0]; + + expect(data.name).to.deep.equal(expectedData.name); + expect(data.ext.rx).to.deep.equal(expectedData.ext.rx); + expect(data.ext.params).to.deep.equal(expectedData.ext.params); + done(); + }, TIMEOUT); + }); + + describe('before rxApi is loaded', function () { + const moveEventTheories = [ + [ + new PointerEvent('pointermove', { clientX: 1, clientY: 2 }), + { x: 1, y: 2 }, + 'pointer move', + ] + ]; + + moveEventTheories.forEach(([event, expected, _description]) => { + it('adds move event', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + + window.dispatchEvent(event); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + + let events = JSON.parse(atob(ext.events)); + + expect(events.ui.position.x).to.be.deep.equal(expected.x); + expect(events.ui.position.y).to.be.deep.equal(expected.y); + expect(Sinon.match.number.test(events.ui.position.timestampMs)).to.be.true; + done(); + }, TIMEOUT); + }); + }); + + it('adds screen event', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + + // Cannot change the window size from JS + // So we take the current size as expectation + const width = window.innerWidth; + const height = window.innerHeight; + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + + let events = JSON.parse(atob(ext.events)); + + expect(events.ui.screen.topLeft).to.be.deep.equal({ x: 0, y: 0 }, 'screen top left'); + expect(events.ui.screen.width).to.be.deep.equal(width, 'screen width'); + expect(events.ui.screen.height).to.be.deep.equal(height, 'screen height'); + expect(Sinon.match.number.test(events.ui.screen.timestampMs), 'screen timestamp').to.be.true; + done(); + }, TIMEOUT); + }); + }) + }); + + describe('after rxApi is loaded', function () { + it('does not add event', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + contxtfulSubmodule.init(config); + window.dispatchEvent(RX_CONNECTOR_IS_READY_EVENT); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + setTimeout(() => { + const onDoneSpy = sinon.spy(); + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, config); + + let ext = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]].user.data[0].ext; + + let events = ext.events; + + expect(events).to.be.undefined; + done(); + }, TIMEOUT); + }); + }) }); diff --git a/test/spec/modules/conversantAnalyticsAdapter_spec.js b/test/spec/modules/conversantAnalyticsAdapter_spec.js index 75cb63b02f6..f46de31b19c 100644 --- a/test/spec/modules/conversantAnalyticsAdapter_spec.js +++ b/test/spec/modules/conversantAnalyticsAdapter_spec.js @@ -14,7 +14,7 @@ describe('Conversant analytics adapter tests', function() { let clock; // clock stub from sinon to mock our cache cleanup interval let logInfoStub; - const PREBID_VERSION = '1.2'; + const PREBID_VERSION = '$prebid.version$'; const SITE_ID = 108060; let requests; diff --git a/test/spec/modules/conversantBidAdapter_spec.js b/test/spec/modules/conversantBidAdapter_spec.js index 9503a050092..2b2d44b3b06 100644 --- a/test/spec/modules/conversantBidAdapter_spec.js +++ b/test/spec/modules/conversantBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, storage} from 'modules/conversantBidAdapter.js'; import * as utils from 'src/utils.js'; +import {deepSetValue} from 'src/utils.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {deepAccess} from 'src/utils'; // load modules that register ORTB processors @@ -8,7 +9,7 @@ import 'src/prebid.js' import 'modules/currency.js'; import 'modules/userId/index.js'; // handles eids import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; // handles schain import {hook} from '../../../src/hook.js' @@ -440,6 +441,13 @@ describe('Conversant adapter tests', function() { expect(payload.site.content).to.have.property('title'); }); + it('Verify currency', () => { + const bidderRequest = { timeout: 9999, ortb2: {cur: ['EUR']} }; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = request.data; + expect(payload.cur).deep.equal(['USD']); + }) + it('Verify supply chain data', () => { const bidderRequest = {refererInfo: {page: 'http://test.com?a=b&c=123'}}; const schain = {complete: 1, ver: '1.0', nodes: [{asi: 'bidderA.com', sid: '00001', hp: 1}]}; @@ -465,7 +473,7 @@ describe('Conversant adapter tests', function() { before(() => { request = spec.buildRequests(bidRequests, {}); - response = spec.interpretResponse(bidResponses, request); + response = spec.interpretResponse(bidResponses, request).bids; }); it('Banner', function() { @@ -552,16 +560,10 @@ describe('Conversant adapter tests', function() { // clone bidRequests let requests = utils.deepClone(bidRequests); - const uid = {pubcid: '112233', idl_env: '334455'}; const eidArray = [{'source': 'pubcid.org', 'uids': [{'id': '112233', 'atype': 1}]}, {'source': 'liveramp.com', 'uids': [{'id': '334455', 'atype': 3}]}]; - // add pubcid to every entry - requests.forEach((unit) => { - Object.assign(unit, {userId: uid}); - Object.assign(unit, {userIdAsEids: eidArray}); - }); // construct http post payload - const payload = spec.buildRequests(requests, {}).data; + const payload = spec.buildRequests(requests, {ortb2: {user: {ext: {eids: eidArray}}}}).data; expect(payload).to.have.deep.nested.property('user.ext.eids', [ {source: 'pubcid.org', uids: [{id: '112233', atype: 1}]}, {source: 'liveramp.com', uids: [{id: '334455', atype: 3}]} diff --git a/test/spec/modules/copper6sspBidAdapter_spec.js b/test/spec/modules/copper6sspBidAdapter_spec.js new file mode 100644 index 00000000000..a97ef3ecbe5 --- /dev/null +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -0,0 +1,515 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/copper6sspBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'copper6ssp'; + +describe('Copper6SSPBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + 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://endpoint.copper6.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', + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://сsync.copper6.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://сsync.copper6.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://сsync.copper6.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/cpmstarBidAdapter_spec.js b/test/spec/modules/cpmstarBidAdapter_spec.js index 285fca9690a..f9b1ba59cde 100755 --- a/test/spec/modules/cpmstarBidAdapter_spec.js +++ b/test/spec/modules/cpmstarBidAdapter_spec.js @@ -149,7 +149,7 @@ describe('Cpmstar Bid Adapter', function () { }; 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,,,'); + expect(requests[0].url).to.include('&schain=1.0%2C1%21exchange1.com%2C1234%2C1%2C%2C%2C%21exchange2.com%2Cabcd%2C1%2C%2C%2C'); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/craftBidAdapter_spec.js b/test/spec/modules/craftBidAdapter_spec.js index dfdbebde738..aeb17f37161 100644 --- a/test/spec/modules/craftBidAdapter_spec.js +++ b/test/spec/modules/craftBidAdapter_spec.js @@ -40,21 +40,21 @@ describe('craftAdapter', function () { }); it('should return false when params.sitekey not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { placementId: '1234abcd' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when params.placementId not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { sitekey: 'craft-prebid-example' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when AMP cotext found', function () { diff --git a/test/spec/modules/criteoBidAdapter_spec.js b/test/spec/modules/criteoBidAdapter_spec.js index def35b13955..30de96e33d0 100755 --- a/test/spec/modules/criteoBidAdapter_spec.js +++ b/test/spec/modules/criteoBidAdapter_spec.js @@ -1,17 +1,20 @@ import { expect } from 'chai'; import { - tryGetCriteoFastBid, spec, storage, - PROFILE_ID_PUBLISHERTAG, - ADAPTER_VERSION, - canFastBid, getFastBidUrl, FAST_BID_VERSION_CURRENT } from 'modules/criteoBidAdapter.js'; import * as utils from 'src/utils.js'; import * as refererDetection from 'src/refererDetection.js'; import * as ajax from 'src/ajax.js'; import { config } from '../../../src/config.js'; import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import {syncAddFPDToBidderRequest} from '../../helpers/fpd'; +import 'modules/userId/index.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/schain.js'; +import {hook} from '../../../src/hook'; describe('The Criteo bidding adapter', function () { let utilsMock, sandbox, ajaxStub; @@ -120,7 +123,9 @@ describe('The Criteo bidding adapter', function () { getCookieStub, setCookieStub, getDataFromLocalStorageStub, - removeDataFromLocalStorageStub; + setDataInLocalStorageStub, + removeDataFromLocalStorageStub, + triggerPixelStub; beforeEach(function () { getConfigStub = sinon.stub(config, 'getConfig'); @@ -142,7 +147,10 @@ describe('The Criteo bidding adapter', function () { getCookieStub = sinon.stub(storage, 'getCookie'); setCookieStub = sinon.stub(storage, 'setCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + setDataInLocalStorageStub = sinon.stub(storage, 'setDataInLocalStorage'); removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); + + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); }); afterEach(function () { @@ -154,15 +162,9 @@ describe('The Criteo bidding adapter', function () { getCookieStub.restore(); setCookieStub.restore(); getDataFromLocalStorageStub.restore(); + setDataInLocalStorageStub.restore(); removeDataFromLocalStorageStub.restore(); - }); - - it('should not trigger sync if publisher is using fast bid', function () { - getConfigStub.withArgs('criteo.fastBidVersion').returns('latest'); - - const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); - - expect(userSyncs).to.eql([]); + triggerPixelStub.restore(); }); it('should not trigger sync if publisher did not enable iframe based syncs', function () { @@ -306,6 +308,62 @@ describe('The Criteo bidding adapter', function () { expect(removeDataFromLocalStorageStub.called).to.be.false; expect(ajaxStub.called).to.be.false; }); + + it('should trigger sync pixel from iframe response', function (done) { + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + const event = new MessageEvent('message', { + data: { + requestId: '123456', + callbacks: [ + 'https://example.com/pixel1', + 'https://example.com/pixel2' + ] + }, + origin: 'https://gum.criteo.com' + }); + + window.dispatchEvent(event); + setTimeout(() => { + expect(triggerPixelStub.calledTwice).to.be.true; + expect(triggerPixelStub.firstCall.calledWith('https://example.com/pixel1')).to.be.true; + expect(triggerPixelStub.secondCall.calledWith('https://example.com/pixel2')).to.be.true; + + done(); + }, 0); + }); + + it('should write cookie only on TLD+1 level', function(done) { + const cookies = {}; + + const userSyncs = spec.getUserSyncs(syncOptionsIframeEnabled, undefined, undefined, undefined); + + setCookieStub.callsFake((name, value, expires, _, domain) => { + if (domain != '.com') { + cookies[name] = value; + } + }); + + getCookieStub.callsFake((name) => cookies[name]); + + const event = new MessageEvent('message', { + data: { + requestId: '123456', + bundle: 'bundle' + }, + origin: 'https://gum.criteo.com' + }); + + window.dispatchEvent(event); + setTimeout(() => { + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.abc.com')).to.be.true; + expect(setCookieStub.calledWith('cto_bundle', 'bundle', sinon.match.string, null, '.www.abc.com')).to.be.false; + expect(cookies).to.deep.equal({ 'cto_bundle': 'bundle' }); + + done(); + }, 0); + }); }); describe('isBidRequestValid', function () { @@ -602,8 +660,8 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 1, - consentString: 'concentDataString', + gdprApplies: true, + consentString: 'consentDataString', vendorData: { vendorConsents: { '91': 1 @@ -615,6 +673,10 @@ describe('The Criteo bidding adapter', function () { let localStorageIsEnabledStub; + before(() => { + hook.ready(); + }); + this.beforeEach(function () { localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); localStorageIsEnabledStub.returns(true); @@ -628,13 +690,11 @@ describe('The Criteo bidding adapter', function () { it('should properly build a request using random uuid as auction id', function () { const generateUUIDStub = sinon.stub(utils, 'generateUUID'); generateUUIDStub.returns('def'); - const bidderRequest = { - }; + const bidderRequest = {}; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -643,7 +703,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.id).to.equal('def'); generateUUIDStub.restore(); @@ -661,7 +721,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -670,7 +729,7 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.source.tid).to.equal('abc'); }); @@ -689,49 +748,18 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); }); it('should properly transmit bidId if available', function () { - const bidderRequest = { - ortb2: { - source: { - tid: 'abc' - } - } - }; - const bidRequests = [ - { - bidId: 'bidId', - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: {} - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].slotid).to.equal('bidId'); - }); - - it('should properly build a request if refererInfo is not provided', function () { const bidderRequest = {}; const bidRequests = [ { + bidId: 'bidId', bidder: 'criteo', adUnitCode: 'bid-123', - ortb2Imp: { - ext: { - tid: 'transaction-123', - }, - }, mediaTypes: { banner: { sizes: [[728, 90]] @@ -740,9 +768,9 @@ describe('The Criteo bidding adapter', function () { params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(''); + expect(ortbRequest.imp[0].id).to.equal('bidId'); }); it('should properly build a zoneId request', function () { @@ -763,77 +791,24 @@ describe('The Criteo bidding adapter', function () { params: { zoneId: 123, publisherSubId: '123', - nativeCallback: function () { }, integrationMode: 'amp' }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=1&im=1&debug=1&nolog=1/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&im=1&debug=[01]&nolog=[01]$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.slots).to.have.lengthOf(1); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('728x90'); - expect(ortbRequest.slots[0].zoneid).to.equal(123); - expect(ortbRequest.gdprConsent.consentData).to.equal('concentDataString'); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(true); - expect(ortbRequest.gdprConsent.version).to.equal(1); - }); - - it('should keep undefined sizes for non native banner', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [[undefined, undefined]] - } - }, - params: {}, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); - }); - - it('should keep undefined size for non native banner', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [undefined, undefined] - } - }, - params: {}, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('undefinedxundefined'); - }); - - it('should properly detect and forward native flag', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [[undefined, undefined]] - } - }, - params: { - nativeCallback: function () { } - }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); it('should properly forward eids', function () { @@ -841,25 +816,33 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, - userIdAsEids: [ - { - source: 'criteo.com', - uids: [{ - id: 'abc', - atype: 1 - }] - } - ], params: {} }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const br = { + ...bidderRequest, + ortb2: { + user: { + ext: { + eids: [ + { + source: 'criteo.com', + uids: [{ + id: 'abc', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); const ortbRequest = request.data; expect(ortbRequest.user.ext.eids).to.deep.equal([ { @@ -872,79 +855,81 @@ describe('The Criteo bidding adapter', function () { ]); }); - it('should properly detect and forward native flag', function () { - const bidRequests = [ - { - mediaTypes: { - banner: { - sizes: [undefined, undefined] - } - }, - params: { - nativeCallback: function () { } + if (FEATURES.NATIVE) { + it('should properly build a native request without assets', function () { + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + params: {} }, - }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); - }); + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].native.request_native).to.not.be.null; + expect(ortbRequest.imp[0].native.request_native.assets).to.be.undefined; + }); + } - it('should map ortb native assets to slot ext assets', function () { - const assets = [{ - required: 1, - id: 1, - img: { - type: 3, - wmin: 100, - hmin: 100, - } - }, - { - required: 1, - id: 2, - title: { - len: 140, - } - }, - { - required: 1, - id: 3, - data: { - type: 1, - } - }, - { - required: 0, - id: 4, - data: { - type: 2, - } - }, - { - required: 0, - id: 5, - img: { - type: 1, - wmin: 20, - hmin: 20, - } - }]; - const bidRequests = [ + if (FEATURES.NATIVE) { + it('should properly build a native request with assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, { - nativeOrtbRequest: { - assets: assets - }, - params: { - nativeCallback: function () { } - }, + required: 1, + id: 2, + title: { + len: 140, + } }, - ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const ortbRequest = request.data; - expect(ortbRequest.slots[0].native).to.equal(true); - expect(ortbRequest.slots[0].ext.assets).to.deep.equal(assets); - }); + { + required: 1, + id: 3, + data: { + type: 1, + } + }, + { + required: 0, + id: 4, + data: { + type: 2, + } + }, + { + required: 0, + id: 5, + img: { + type: 1, + wmin: 20, + hmin: 20, + } + }]; + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: assets + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].native.request_native).to.not.be.null; + expect(ortbRequest.imp[0].native.request_native.assets).to.deep.equal(assets); + }); + } it('should properly build a networkId request', function () { const bidderRequest = { @@ -954,7 +939,7 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 0, + gdprApplies: false, consentString: undefined, vendorData: { vendorConsents: { @@ -982,23 +967,23 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.publisher.networkid).to.equal(456); - expect(ortbRequest.slots).to.have.lengthOf(1); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(2); - expect(ortbRequest.slots[0].sizes[0]).to.equal('300x250'); - expect(ortbRequest.slots[0].sizes[1]).to.equal('728x90'); - expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(false); - }); - - it('should properly build a mixed request', function () { + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(90); + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs.ext.gdpr).to.equal(0); + }); + + it('should properly build a mixed request with both a zoneId and a networkId', function () { const bidderRequest = { refererInfo: { page: refererUrl, @@ -1042,23 +1027,26 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.url).to.match(/^https:\/\/bidder\.criteo\.com\/cdb\?profileId=207&av=\d+&wv=[^&]+&cb=\d/); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); expect(request.method).to.equal('POST'); const ortbRequest = request.data; - expect(ortbRequest.publisher.url).to.equal(refererUrl); - expect(ortbRequest.publisher.networkid).to.equal(456); - expect(ortbRequest.slots).to.have.lengthOf(2); - expect(ortbRequest.slots[0].impid).to.equal('bid-123'); - expect(ortbRequest.slots[0].transactionid).to.equal('transaction-123'); - expect(ortbRequest.slots[0].sizes).to.have.lengthOf(1); - expect(ortbRequest.slots[0].sizes[0]).to.equal('728x90'); - expect(ortbRequest.slots[1].impid).to.equal('bid-234'); - expect(ortbRequest.slots[1].transactionid).to.equal('transaction-234'); - expect(ortbRequest.slots[1].sizes).to.have.lengthOf(2); - expect(ortbRequest.slots[1].sizes[0]).to.equal('300x250'); - expect(ortbRequest.slots[1].sizes[1]).to.equal('728x90'); - expect(ortbRequest.gdprConsent).to.equal(undefined); + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(2); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.tid).to.equal('transaction-123'); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.imp[1].tagid).to.equal('bid-234'); + expect(ortbRequest.imp[1].banner.format).to.have.lengthOf(2); + expect(ortbRequest.imp[1].banner.format[0].w).to.equal(300); + expect(ortbRequest.imp[1].banner.format[0].h).to.equal(250); + expect(ortbRequest.imp[1].banner.format[1].w).to.equal(728); + expect(ortbRequest.imp[1].banner.format[1].h).to.equal(90); + expect(ortbRequest.imp[1].ext.tid).to.equal('transaction-234'); + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); }); it('should properly build a request with undefined gdpr consent fields when they are not provided', function () { @@ -1066,7 +1054,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1082,9 +1069,9 @@ describe('The Criteo bidding adapter', function () { gdprConsent: {}, }; - const ortbRequest = spec.buildRequests(bidRequests, bidderRequest).data; - expect(ortbRequest.gdprConsent.consentData).to.equal(undefined); - expect(ortbRequest.gdprConsent.gdprApplies).to.equal(undefined); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.user?.ext?.consent).to.equal(undefined); + expect(ortbRequest.regs?.ext?.gdpr).to.equal(undefined); }); it('should properly build a request with ccpa consent field', function () { @@ -1092,7 +1079,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1108,42 +1094,43 @@ describe('The Criteo bidding adapter', function () { uspConsent: '1YNY', }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.uspIab).to.equal('1YNY'); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.us_privacy).to.equal('1YNY'); }); - it('should properly build a request with site and app ortb fields', function () { - const bidRequests = []; - let app = { - publisher: { - id: 'appPublisherId' - } - }; - let site = { - publisher: { - id: 'sitePublisherId' - } - }; + it('should properly build a request with overridden tmax', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + zoneId: 123, + }, + }, + ]; const bidderRequest = { - ortb2: { - app: app, - site: site - } + timeout: 1234 }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.app).to.equal(app); - expect(request.data.site).to.equal(site); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.tmax).to.equal(1234); }); it('should properly build a request with device sua field', function () { - const sua = {} + const sua = { + platform: { + brand: 'abc' + } + } const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1164,9 +1151,9 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user.ext.sua).to.not.be.null; - expect(request.data.user.ext.sua).to.equal(sua); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.device.ext.sua).not.to.be.null; + expect(ortbRequest.device.ext.sua.platform.brand).to.equal('abc'); }); it('should properly build a request with gpp consent field', function () { @@ -1174,7 +1161,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1192,10 +1178,9 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.regs).to.not.be.null; - expect(request.data.regs.gpp).to.equal('gpp_consent_string'); - expect(request.data.regs.gpp_sid).to.deep.equal([0, 1, 2]); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); + expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); }); it('should properly build a request with dsa object', function () { @@ -1203,7 +1188,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1234,10 +1218,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.regs).to.not.be.null; - expect(request.data.regs.ext).to.not.be.null; - expect(request.data.regs.ext.dsa).to.deep.equal(dsa); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.ext.dsa).to.deep.equal(dsa); }); it('should properly build a request with schain object', function () { @@ -1249,7 +1231,6 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', schain: expectedSchain, adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1261,8 +1242,8 @@ describe('The Criteo bidding adapter', function () { }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.source.ext.schain).to.equal(expectedSchain); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.source.ext.schain).to.equal(expectedSchain); }); it('should properly build a request with bcat field', function () { @@ -1271,7 +1252,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1288,9 +1268,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bcat).to.not.be.null; - expect(request.data.bcat).to.equal(bcat); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); }); it('should properly build a request with badv field', function () { @@ -1299,7 +1278,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1316,9 +1294,8 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.badv).to.not.be.null; - expect(request.data.badv).to.equal(badv); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); }); it('should properly build a request with bapp field', function () { @@ -1327,7 +1304,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1344,283 +1320,216 @@ describe('The Criteo bidding adapter', function () { } }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bapp).to.not.be.null; - expect(request.data.bapp).to.equal(bapp); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); }); - it('should properly build a request with if ccpa consent field is not provided', function () { + if (FEATURES.VIDEO) { + it('should properly build a video request', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[640, 480]], + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 480], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 480, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + expect(ortbRequest.imp[0].video.w).to.equal(640); + expect(ortbRequest.imp[0].video.h).to.equal(480); + expect(ortbRequest.imp[0].video.linearity).to.equal(1); + expect(ortbRequest.imp[0].video.skipmin).to.equal(30); + expect(ortbRequest.imp[0].video.skipafter).to.equal(30); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(10000); + expect(ortbRequest.imp[0].video.maxbitrate).to.equal(48000); + expect(ortbRequest.imp[0].video.delivery).to.deep.equal([1, 2, 3]); + expect(ortbRequest.imp[0].video.pos).to.equal(1); + expect(ortbRequest.imp[0].video.playbackend).to.equal(1); + expect(ortbRequest.imp[0].video.ext.context).to.equal('inbanner'); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['640x480']); + expect(ortbRequest.imp[0].video.ext.plcmt).to.equal(3); + expect(ortbRequest.imp[0].video.ext.poddur).to.equal(30); + expect(ortbRequest.imp[0].video.ext.rqddurs).to.deep.equal([1, 30]); + }); + } + + if (FEATURES.VIDEO) { + it('should properly build a video request with more than one player size', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[640, 480], [800, 600]], + mediaTypes: { + video: { + playerSize: [[640, 480], [800, 600]], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3] + } + }, + params: { + zoneId: 123, + video: { + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + expect(ortbRequest.imp[0].video.w).to.equal(640); + expect(ortbRequest.imp[0].video.h).to.equal(480); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['640x480', '800x600']); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + }); + } + + if (FEATURES.VIDEO) { + it('should properly build a video request when mediaTypes.video.skip=0', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + sizes: [[300, 250]], + 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: 456 + } + } + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + expect(request.url).to.match(/^https:\/\/grid-bidder\.criteo\.com\/openrtb_2_5\/pbjs\/auction\/request\?profileId=207&av=\d+&wv=[^&]+&cb=\d+&lsavail=[01]&debug=[01]&nolog=[01]&networkId=456$/); + expect(request.method).to.equal('POST'); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/MPV', 'video/H264', 'video/webm', 'video/ogg']); + expect(ortbRequest.imp[0].video.minduration).to.equal(1); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([2, 3, 4, 5, 6]); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2, 3, 4, 5, 6]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([1, 2, 3, 4, 5, 6, 7, 8]); + expect(ortbRequest.imp[0].video.skip).to.equal(0); + expect(ortbRequest.imp[0].video.w).to.equal(300); + expect(ortbRequest.imp[0].video.h).to.equal(250); + expect(ortbRequest.imp[0].video.ext.playersizes).to.deep.equal(['300x250']); + }); + } + + it('should properly build a request without first party data', function () { const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - }, + zoneId: 123 + } }, ]; - const bidderRequest = { - timeout: 3000 - }; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.uspIab).to.equal(undefined); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2: {} })).data; + expect(ortbRequest.site.page).to.equal(refererUrl); + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].tagid).to.equal('bid-123'); + expect(ortbRequest.imp[0].banner.format).to.have.lengthOf(1); + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(728); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(90); + expect(ortbRequest.imp[0].ext.bidder.zoneid).to.equal(123); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + expect(ortbRequest.regs.ext.gdpr).to.equal(1); + expect(ortbRequest.regs.ext.gdprversion).to.equal(1); }); - it('should properly build a video request', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[640, 480]], - mediaTypes: { - video: { - context: 'instream', - playerSize: [640, 480], - mimes: ['video/mp4', 'video/x-flv'], - maxduration: 30, - api: [1, 2], - protocols: [2, 3], - plcmt: 3, - w: 640, - h: 480, - linearity: 1, - skipmin: 30, - skipafter: 30, - minbitrate: 10000, - maxbitrate: 48000, - delivery: [1, 2, 3], - pos: 1, - playbackend: 1, - adPodDurationSec: 30, - durationRangeSec: [1, 30], - } - }, - params: { - zoneId: 123, - video: { - skip: 1, - minduration: 5, - startdelay: 5, - playbackmethod: [1, 3], - placement: 2 - } - }, - }, - ]; - 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.context).to.equal('instream'); - expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(ortbRequest.slots[0].sizes).to.deep.equal([]); - expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480']); - expect(ortbRequest.slots[0].video.maxduration).to.equal(30); - expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); - expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); - expect(ortbRequest.slots[0].video.skip).to.equal(1); - expect(ortbRequest.slots[0].video.minduration).to.equal(5); - expect(ortbRequest.slots[0].video.startdelay).to.equal(5); - expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); - expect(ortbRequest.slots[0].video.placement).to.equal(2); - expect(ortbRequest.slots[0].video.plcmt).to.equal(3); - expect(ortbRequest.slots[0].video.w).to.equal(640); - expect(ortbRequest.slots[0].video.h).to.equal(480); - expect(ortbRequest.slots[0].video.linearity).to.equal(1); - expect(ortbRequest.slots[0].video.skipmin).to.equal(30); - expect(ortbRequest.slots[0].video.skipafter).to.equal(30); - expect(ortbRequest.slots[0].video.minbitrate).to.equal(10000); - expect(ortbRequest.slots[0].video.maxbitrate).to.equal(48000); - expect(ortbRequest.slots[0].video.delivery).to.deep.equal([1, 2, 3]); - expect(ortbRequest.slots[0].video.pos).to.equal(1); - expect(ortbRequest.slots[0].video.playbackend).to.equal(1); - expect(ortbRequest.slots[0].video.adPodDurationSec).to.equal(30); - expect(ortbRequest.slots[0].video.durationRangeSec).to.deep.equal([1, 30]); - }); - - it('should properly build a video request with more than one player size', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[640, 480], [800, 600]], - mediaTypes: { - video: { - playerSize: [[640, 480], [800, 600]], - mimes: ['video/mp4', 'video/x-flv'], - maxduration: 30, - api: [1, 2], - protocols: [2, 3] - } - }, - params: { - zoneId: 123, - video: { - skip: 1, - minduration: 5, - startdelay: 5, - playbackmethod: [1, 3], - placement: 2 - } - }, - }, - ]; - 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].sizes).to.deep.equal([]); - expect(ortbRequest.slots[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); - expect(ortbRequest.slots[0].video.playersizes).to.deep.equal(['640x480', '800x600']); - expect(ortbRequest.slots[0].video.maxduration).to.equal(30); - expect(ortbRequest.slots[0].video.api).to.deep.equal([1, 2]); - expect(ortbRequest.slots[0].video.protocols).to.deep.equal([2, 3]); - expect(ortbRequest.slots[0].video.skip).to.equal(1); - expect(ortbRequest.slots[0].video.minduration).to.equal(5); - expect(ortbRequest.slots[0].video.startdelay).to.equal(5); - expect(ortbRequest.slots[0].video.playbackmethod).to.deep.equal([1, 3]); - 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: [[300, 250]], - 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].sizes).to.deep.equal([]); - 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 = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123, - }, - }, - ]; - config.setConfig({ - criteo: { - ceh: 'hashedemail' - } - }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.user).to.not.be.null; - expect(request.data.user.ceh).to.equal('hashedemail'); - }); - - it('should properly build a request without first party data', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123 - } - }, - ]; - - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); - expect(request.data.publisher.ext).to.equal(undefined); - expect(request.data.user.ext).to.equal(undefined); - expect(request.data.slots[0].ext).to.equal(undefined); - }); - - it('should properly build a request with criteo specific ad unit first party data', function () { - // TODO: this test does not do what it says - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } - } - }, - ]; - - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2: {} }); - expect(request.data.slots[0].ext).to.deep.equal({ - bidfloor: 0.75, - }); - }); - - it('should properly build a request with first party data', function () { - const siteData = { - keywords: ['power tools'], - content: { - data: [{ - name: 'some_provider', - ext: { - segtax: 3 - }, - segment: [ - { 'id': '1001' }, - { 'id': '1002' } - ] - }] + it('should properly build a request with first party data', function () { + const siteData = { + keywords: ['power tools'], + content: { + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }] }, ext: { data: { @@ -1650,7 +1559,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1677,41 +1585,34 @@ describe('The Criteo bidding adapter', function () { user: userData }; - const request = spec.buildRequests(bidRequests, { ...bidderRequest, ortb2 }); - expect(request.data.publisher.ext).to.deep.equal({ data: { pageType: 'article' } }); - expect(request.data.user).to.deep.equal(userData); - expect(request.data.site).to.deep.equal(siteData); - expect(request.data.slots[0].ext).to.deep.equal({ - bidfloor: 0.75, - data: { - someContextAttribute: 'abc' - } - }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.user).to.deep.equal({ ...userData, ext: { ...userData.ext, consent: 'consentDataString' } }); + expect(ortbRequest.site).to.deep.equal({ ...siteData, page: refererUrl, domain: 'criteo.com', publisher: { ...ortbRequest.site.publisher, domain: 'criteo.com' } }); + expect(ortbRequest.imp[0].ext.bidfloor).to.equal(0.75); + expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.equal('abc') }); it('should properly build a request when coppa flag is true', function () { const bidRequests = []; const bidderRequest = {}; config.setConfig({ coppa: true }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.not.be.undefined; - expect(request.data.regs.coppa).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); }); it('should properly build a request when coppa flag is false', function () { const bidRequests = []; const bidderRequest = {}; config.setConfig({ coppa: false }); - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.not.be.undefined; - expect(request.data.regs.coppa).to.equal(0); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(0); }); it('should properly build a request when coppa flag is not defined', function () { const bidRequests = []; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.regs.coppa).to.be.undefined; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; }); it('should properly build a banner request with floors', function () { @@ -1719,7 +1620,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1747,8 +1647,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } @@ -1761,7 +1661,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1775,8 +1674,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'banner': { '300x250': { 'currency': 'EUR', 'floor': 1 }, '728x90': { 'currency': 'EUR', 'floor': 1 } @@ -1789,7 +1688,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { video: { playerSize: [[300, 250], [728, 90]] @@ -1817,8 +1715,8 @@ describe('The Criteo bidding adapter', function () { }, ]; const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.floors).to.deep.equal({ + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ 'video': { '300x250': { 'currency': 'USD', 'floor': 1 }, '728x90': { 'currency': 'USD', 'floor': 2 } @@ -1826,75 +1724,79 @@ describe('The Criteo bidding adapter', function () { }); }); - it('should properly build a multi format request with floors', function () { - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', - mediaTypes: { - banner: { - sizes: [[300, 250], [728, 90]] + if (FEATURES.VIDEO && FEATURES.NATIVE) { + it('should properly build a multi format request with floors', function () { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + banner: { + sizes: [[300, 250], [728, 90]] + }, + video: { + playerSize: [640, 480], + }, + native: {} }, - video: { - playerSize: [640, 480], + params: { + networkId: 456, }, - native: {} - }, - params: { - networkId: 456, - }, - ortb2Imp: { - ext: { - data: { - someContextAttribute: 'abc' + ortb2Imp: { + ext: { + data: { + someContextAttribute: 'abc' + } } - } - }, + }, - getFloor: inputParams => { - if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { - return { - currency: 'USD', - floor: 1.0 - }; - } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { - return { - currency: 'USD', - floor: 2.0 - }; - } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 640 && inputParams.size[1] === 480) { - return { - currency: 'EUR', - floor: 3.2 - }; - } else if (inputParams.mediaType === NATIVE && inputParams.size === '*') { - return { - currency: 'YEN', - floor: 4.99 - }; - } else { - return {} + getFloor: inputParams => { + if (inputParams.mediaType === BANNER && inputParams.size[0] === 300 && inputParams.size[1] === 250) { + return { + currency: 'USD', + floor: 1.0 + }; + } else if (inputParams.mediaType === BANNER && inputParams.size[0] === 728 && inputParams.size[1] === 90) { + return { + currency: 'USD', + floor: 2.0 + }; + } else if (inputParams.mediaType === VIDEO && inputParams.size[0] === 640 && inputParams.size[1] === 480) { + return { + currency: 'EUR', + floor: 3.2 + }; + } else if (inputParams.mediaType === NATIVE && inputParams.size === '*') { + return { + currency: 'YEN', + floor: 4.99 + }; + } else { + return {} + } } + }, + ]; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].video).not.to.be.null; + expect(ortbRequest.imp[0].native.request_native).not.to.be.null; + expect(ortbRequest.imp[0].ext.data.someContextAttribute).to.deep.equal('abc'); + expect(ortbRequest.imp[0].ext.floors).to.deep.equal({ + 'banner': { + '300x250': { 'currency': 'USD', 'floor': 1 }, + '728x90': { 'currency': 'USD', 'floor': 2 } + }, + 'video': { + '640x480': { 'currency': 'EUR', 'floor': 3.2 } + }, + 'native': { + '*': { 'currency': 'YEN', 'floor': 4.99 } } - }, - ]; - const bidderRequest = {}; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.data.someContextAttribute).to.deep.equal('abc'); - expect(request.data.slots[0].ext.floors).to.deep.equal({ - 'banner': { - '300x250': { 'currency': 'USD', 'floor': 1 }, - '728x90': { 'currency': 'USD', 'floor': 2 } - }, - 'video': { - '640x480': { 'currency': 'EUR', 'floor': 3.2 } - }, - 'native': { - '*': { 'currency': 'YEN', 'floor': 4.99 } - } + }); }); - }); + } it('should properly build a request when imp.rwdd is present', function () { const bidderRequest = {}; @@ -1902,32 +1804,22 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { - rwdd: 1, - ext: { - data: { - someContextAttribute: 'abc' - } - } + rwdd: 1 } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].rwdd).to.be.not.null; - expect(request.data.slots[0].rwdd).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.rwdd).to.equal(1); }); it('should properly build a request when imp.rwdd is false', function () { @@ -1936,95 +1828,86 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { - rwdd: 0, - ext: { - data: { - someContextAttribute: 'abc' - } - } + rwdd: 0 } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].rwdd).to.be.undefined; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext?.rwdd).to.equal(0); }); it('should properly build a request when FLEDGE is enabled', function () { const bidderRequest = { - fledgeEnabled: true, + paapi: { + enabled: true + } }; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { ext: { - ae: 1 + igs: { + ae: 1 + } } } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext.ae).to.equal(1); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs.ae).to.equal(1); }); it('should properly build a request when FLEDGE is disabled', function () { const bidderRequest = { - fledgeEnabled: false, + paapi: { + enabled: false + }, }; const bidRequests = [ { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] } }, params: { - zoneId: 123, - ext: { - bidfloor: 0.75 - } + zoneId: 123 }, ortb2Imp: { ext: { - ae: 1 + igs: { + ae: 1 + } } } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.slots[0].ext).to.not.have.property('ae'); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].ext.igs?.ae).to.be.undefined; }); it('should properly transmit the pubid and slot uid if available', function () { @@ -2075,12 +1958,11 @@ describe('The Criteo bidding adapter', function () { }, }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; - expect(ortbRequest.publisher.id).to.be.undefined; expect(ortbRequest.site.publisher.id).to.equal('pub-888'); - expect(request.data.slots[0].ext.bidder).to.be.undefined; - expect(request.data.slots[1].ext.bidder.uid).to.equal(888); + expect(ortbRequest.imp[0].ext.bidder.uid).to.be.undefined; + expect(ortbRequest.imp[1].ext.bidder.uid).to.equal(888); }); it('should properly transmit device.ext.cdep if available', function () { @@ -2094,136 +1976,243 @@ describe('The Criteo bidding adapter', function () { } }; const bidRequests = []; - const request = spec.buildRequests(bidRequests, bidderRequest); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const ortbRequest = request.data; expect(ortbRequest.device.ext.cdep).to.equal('cookieDeprecationLabel'); }); }); describe('interpretResponse', function () { - it('should return an empty array when parsing a no bid response', function () { + const refererUrl = 'https://criteo.com?pbt_debug=1&pbt_nolog=1'; + const bidderRequest = { + refererInfo: { + page: refererUrl, + topmostLocation: refererUrl + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '91': 1 + }, + }, + apiVersion: 1, + }, + }; + + function mockResponse(winningBidId, mediaType) { + return { + id: 'test-requestId', + seatbid: [ + { + seat: 'criteo', + bid: [ + { + id: 'test-bidderId', + impid: winningBidId, + price: 1.23, + adomain: ['criteo.com'], + bundle: '', + iurl: 'http://some_image/', + cid: '123456', + crid: 'test-crId', + dealid: 'deal-code', + w: 728, + h: 90, + adm: 'test-ad', + adm_native: mediaType === NATIVE ? { + ver: '1.2', + assets: [ + { + id: 10, + title: { + text: 'Some product' + } + }, + { + id: 11, + img: { + type: 3, + url: 'https://main_image_url.com', + w: 400, + h: 400 + } + }, + { + id: 12, + data: { + value: 'Some product' + } + }, + { + id: 13, + data: { + value: '1,499 TL' + } + }, + { + id: 15, + data: { + value: 'CTA' + }, + link: { + url: 'https://cta_url.com' + } + }, + { + id: 17, + img: { + type: 1, + url: 'https://main_image_url.com', + w: 200, + h: 200 + }, + link: { + url: 'https://icon_image_url.com' + } + }, + { + id: 16, + data: { + value: 'Some brand' + } + } + ], + eventtrackers: [ + { + event: 1, + method: 1, + url: 'https://eventtrackers.com' + }, + { + event: 1, + method: 1, + url: 'https://test_in_isolation.criteo.com/tpd?dd=HTlW9l9xTEZqRHVlSHFiSWx5Q2VQMlEwSTJhNCUyQkxNazQ1Y29LR3ZmS2VTSDFsUGdkRHNoWjQ2UWp0SGtVZ1RTbHI0TFRpTlVqNWxiUkZOeGVFNjVraW53R0loRVJQNDJOY2R1eWxVdjBBQ1BEdVFvTyUyRlg3aWJaeUFha3UyemNNVGpmJTJCS1prc0FwRjZRJTJCQ2dpaFBJeVhZRmQlMkZURVZocUFRdm03OTdFZHZSbURNZWt4Uzh2M1NSUUxmTmhaTnNnRXd4VkZlOTdJOXdnNGZjaVolMkZWYmdYVjJJMkQ0eGxQaFIwQmVtWk1sQ09tNXlGY0Nwc09GTDladzExJTJGVExGNXJsdGpneERDeTMlMkJuNUlUcEU4NDFLMTZPc2ZoWFUwMmpGbDFpVjBPZUVtTlEwaWNOeHRyRFYyenRKd0lpJTJGTTElMkY1WGZ3Smo3aTh0bUJzdzZRdlZUSXppanNkamo3ekZNZjhKdjl2VDJ5eHV1YnVzdmdRdk5iWnprNXVFMVdmbGs0QU1QY0ozZQ' + } + ], + privacy: 'https://cta_url.com', + ext: { + privacy: { + imageurl: 'https://icon_image_url.com', + clickurl: 'https://cta_url.com', + longlegaltext: '' + } + } + } : undefined, + ext: { + mediatype: mediaType, + displayurl: mediaType === VIDEO ? 'http://test-ad' : undefined, + dsa: { + adrender: 1 + }, + meta: { + networkName: 'Criteo' + }, + videoPlayerType: mediaType === VIDEO ? 'RadiantMediaPlayer' : undefined, + videoPlayerConfig: mediaType === VIDEO ? {} : undefined, + cur: 'CUR' + } + } + ] + } + ] + }; + } + + it('should return an empty array when parsing an empty bid response', function () { + const bidRequests = []; const response = {}; - const request = { bidRequests: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); const bids = spec.interpretResponse(response, request); expect(bids).to.have.lengthOf(0); }); - it('should properly parse a bid response with a networkId', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - meta: { - networkName: 'Criteo' - } - } - }], + it('should return an empty array when parsing a well-formed no bid response', function () { + const bidRequests = []; + const response = { seatbid: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should properly parse a banner bid response', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - networkId: 456, - } - }] - }; - const bids = spec.interpretResponse(response, request); + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') 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].currency).to.equal('CUR'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); - expect(bids[0].dealId).to.equal('myDealCode'); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); }); - it('should properly parse a bid response with dsa', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - dsa: { - adrender: 1 - }, - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response with a video', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { - banner: { - sizes: [[728, 90]] + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] } }, params: { - networkId: 456, - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].meta.dsa.adrender).to.equal(1); - }); + zoneId: 123, + }, + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer).to.equal(undefined); + }); + } - it('should properly parse a bid response with a networkId with twin ad unit banner win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response with an outstream video', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { video: { - context: 'instream', + context: 'outstream', mimes: ['video/mpeg'], playerSize: [640, 480], protocols: [5, 6], @@ -2234,68 +2223,123 @@ describe('The Criteo bidding adapter', function () { params: { networkId: 456, }, - }, { + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer.url).to.equal('https://static.criteo.net/js/ld/publishertag.renderer.js'); + expect(typeof bids[0].renderer.config.documentResolver).to.equal('function'); + expect(typeof bids[0].renderer._render).to.equal('function'); + }); + } + + if (FEATURES.NATIVE) { + it('should properly parse a native bid response', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', - bidId: 'test-bidId2', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, + bidId: 'test-bidId', params: { - networkId: 456, + zoneId: '123', + }, + native: true, + }]; + const response = mockResponse('test-bidId', NATIVE); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.equal(response.seatbid[0].bid[0].adm); // adm_native field was moved to adm + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + } + + it('should properly parse a bid response when banner win with twin ad units', function () { + const bidRequests = [{ + adUnitCode: 'test-requestId', + bidId: 'test-bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 480], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] } - }] - }; - const bids = spec.interpretResponse(response, request); + }, + params: { + networkId: 456, + }, + }, { + adUnitCode: 'test-requestId', + bidId: 'test-bidId2', + mediaTypes: { + banner: { + sizes: [[728, 90]] + } + }, + params: { + networkId: 456, + } + }]; + const response = mockResponse('test-bidId2', BANNER); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(BANNER); expect(bids[0].requestId).to.equal('test-bidId2'); + expect(bids[0].seatBidId).to.equal('test-bidderId') 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].currency).to.equal('CUR'); expect(bids[0].width).to.equal(728); expect(bids[0].height).to.equal(90); - expect(bids[0].dealId).to.equal('myDealCode'); + expect(bids[0].ad).to.equal('test-ad'); + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); }); - it('should properly parse a bid response with a networkId with twin ad unit video win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true, - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.VIDEO) { + it('should properly parse a bid response when video win with twin ad units', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { video: { context: 'instream', mimes: ['video/mpeg'], - playerSize: [728, 90], + playerSize: [640, 480], protocols: [5, 6], maxduration: 30, api: [1, 2] } }, params: { - networkId: 456, + zoneId: '123' }, }, { adUnitCode: 'test-requestId', @@ -2308,63 +2352,27 @@ describe('The Criteo bidding adapter', function () { params: { networkId: 456, } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].mediaType).to.equal(VIDEO); - }); + }]; + const response = mockResponse('test-bidId', VIDEO); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].vastUrl).to.equal('http://test-ad'); + expect(bids[0].vastXml).to.equal('test-ad'); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(480); + expect(bids[0].renderer).to.equal(undefined); + }); + } - it('should properly parse a bid response with a networkId with twin ad unit native win', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - creativecode: 'test-crId', - width: 728, - height: 90, - deal: 'myDealCode', - adomain: ['criteo.com'], - native: { - 'products': [{ - 'sendTargetingKeys': false, - 'title': 'Product title', - 'description': 'Product desc', - 'price': '100', - 'click_url': 'https://product.click', - 'image': { - 'url': 'https://publisherdirect.criteo.com/publishertag/preprodtest/creative.png', - 'height': 300, - 'width': 300 - }, - 'call_to_action': 'Try it now!' - }], - 'advertiser': { - 'description': 'sponsor', - 'domain': 'criteo.com', - 'logo': { 'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300 } - }, - 'privacy': { - 'optout_click_url': 'https://info.criteo.com/privacy/informations', - 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', - }, - 'impression_pixels': [{ 'url': 'https://my-impression-pixel/test/impression' }, { 'url': 'https://cas.com/lg.com' }] - }, - ext: { - meta: { - networkName: 'Criteo' - } - } - }], - }, - }; - const request = { - bidRequests: [{ + if (FEATURES.NATIVE) { + it('should properly parse a bid response when native win with twin ad units', function () { + const bidRequests = [{ adUnitCode: 'test-requestId', bidId: 'test-bidId', mediaTypes: { @@ -2384,292 +2392,138 @@ describe('The Criteo bidding adapter', function () { params: { networkId: 456, } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].mediaType).to.equal(NATIVE); - }); + }]; + const response = mockResponse('test-bidId', NATIVE); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + expect(bids[0].requestId).to.equal('test-bidId'); + expect(bids[0].seatBidId).to.equal('test-bidderId') + expect(bids[0].cpm).to.equal(1.23); + expect(bids[0].currency).to.equal('CUR'); + expect(bids[0].width).to.equal(728); + expect(bids[0].height).to.equal(90); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.equal(response.seatbid[0].bid[0].adm); // adm_native field was moved to adm + expect(bids[0].creativeId).to.equal('test-crId'); + expect(bids[0].dealId).to.equal('deal-code'); + expect(bids[0].meta.advertiserDomains[0]).to.equal('criteo.com'); + expect(bids[0].meta.networkName).to.equal('Criteo'); + expect(bids[0].meta.dsa.adrender).to.equal(1); + }); + } - it('should properly parse a bid response with a zoneId', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - creative: 'test-ad', - width: 728, - height: 90, - zoneid: 123, - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, + if (FEATURES.NATIVE) { + it('should warn only once if sendTargetingKeys set to true on required fields for native bidRequest', () => { + const bidRequests = [ + { + bidder: 'criteo', + adUnitCode: 'bid-123', + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + publisherSubId: '123' + }, }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - 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].width).to.equal(728); - expect(bids[0].height).to.equal(90); - }); - - it('should properly parse a bid response with a video', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, + { + bidder: 'criteo', + adUnitCode: 'bid-456', + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, + transactionId: 'transaction-456', + sizes: [[728, 90]], + params: { + zoneId: 456, + publisherSubId: '456' + }, }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].mediaType).to.equal(VIDEO); - }); + ]; - it('should properly parse a bid response with a outstream video', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - displayurl: 'http://test-ad', - width: 728, - height: 90, - zoneid: 123, - video: true, - ext: { - videoPlayerType: 'RadiantMediaPlayer', - videoPlayerConfig: { - - } + const nativeParamsWithSendTargetingKeys = [ + { + nativeParams: { + image: { + sendTargetingKeys: true + }, } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, }, - mediaTypes: { - video: { - context: 'outstream' - } - } - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].vastUrl).to.equal('http://test-ad'); - expect(bids[0].renderer.url).to.equal('https://static.criteo.net/js/ld/publishertag.renderer.js'); - expect(typeof bids[0].renderer.config.documentResolver).to.equal('function'); - expect(typeof bids[0].renderer._render).to.equal('function'); - }); - - it('should properly parse a bid response with native', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - bidId: 'abc123', - cpm: 1.23, - width: 728, - height: 90, - zoneid: 123, - native: { - 'products': [{ - 'sendTargetingKeys': false, - 'title': 'Product title', - 'description': 'Product desc', - 'price': '100', - 'click_url': 'https://product.click', - 'image': { - 'url': 'https://publisherdirect.criteo.com/publishertag/preprodtest/creative.png', - 'height': 300, - 'width': 300 - }, - 'call_to_action': 'Try it now!' - }], - 'advertiser': { - 'description': 'sponsor', - 'domain': 'criteo.com', - 'logo': { 'url': 'https://www.criteo.com/images/criteo-logo.svg', 'height': 300, 'width': 300 } + { + nativeParams: { + icon: { + sendTargetingKeys: true }, - 'privacy': { - 'optout_click_url': 'https://info.criteo.com/privacy/informations', - 'optout_image_url': 'https://static.criteo.net/flash/icon/nai_small.png', + } + }, + { + nativeParams: { + clickUrl: { + sendTargetingKeys: true }, - 'impression_pixels': [{ 'url': 'https://my-impression-pixel/test/impression' }, { 'url': 'https://cas.com/lg.com' }] } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: 123, }, - native: true, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - expect(bids[0].requestId).to.equal('test-bidId'); - expect(bids[0].cpm).to.equal(1.23); - expect(bids[0].mediaType).to.equal(NATIVE); - }); - - 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 () { } + { + nativeParams: { + displayUrl: { + sendTargetingKeys: true + }, + } }, - }, - { - bidder: 'criteo', - adUnitCode: 'bid-456', - transactionId: 'transaction-456', - sizes: [[728, 90]], - params: { - zoneId: 456, - publisherSubId: '456', - nativeCallback: function () { } + { + nativeParams: { + privacyLink: { + sendTargetingKeys: true + }, + } }, - }, - ]; - - 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 - }, + { + 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.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, syncAddFPDToBidderRequest(bidderRequest)); + }); + utilsMock.verify(); }); - utilsMock.verify(); - }); - - it('should properly parse a bid response with a zoneId passed as a string', function () { - const response = { - body: { - slots: [{ - impid: 'test-requestId', - cpm: 1.23, - creative: 'test-ad', - width: 728, - height: 90, - zoneid: 123, - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - params: { - zoneId: '123', - }, - }] - }; - const bids = spec.interpretResponse(response, request); - expect(bids).to.have.lengthOf(1); - 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].width).to.equal(728); - expect(bids[0].height).to.equal(90); - }); + } it('should properly parse a bid response with FLEDGE auction configs', function () { let auctionConfig1 = { @@ -2750,34 +2604,6 @@ describe('The Criteo bidding adapter', function () { }, sellerCurrency: '???' }; - const response = { - body: { - ext: { - igi: [{ - impid: 'test-bidId', - igs: [{ - impid: 'test-bidId', - bidId: 'test-bidId', - config: auctionConfig1 - }] - }, { - impid: 'test-bidId-2', - igs: [{ - impid: 'test-bidId-2', - bidId: 'test-bidId-2', - config: auctionConfig2 - }] - }] - }, - }, - }; - const bidderRequest = { - ortb2: { - source: { - tid: 'abc' - } - } - }; const bidRequests = [ { bidId: 'test-bidId', @@ -2810,18 +2636,37 @@ describe('The Criteo bidding adapter', function () { } }, ]; - const request = spec.buildRequests(bidRequests, bidderRequest); - const interpretedResponse = spec.interpretResponse(response, request); + const response = { + ext: { + igi: [{ + impid: 'test-bidId', + igs: [{ + impid: 'test-bidId', + bidId: 'test-bidId', + config: auctionConfig1 + }] + }, { + impid: 'test-bidId-2', + igs: [{ + impid: 'test-bidId-2', + bidId: 'test-bidId-2', + config: auctionConfig2 + }] + }] + }, + }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const interpretedResponse = spec.interpretResponse({ body: response }, request); expect(interpretedResponse).to.have.property('bids'); - expect(interpretedResponse).to.have.property('fledgeAuctionConfigs'); + expect(interpretedResponse).to.have.property('paapi'); expect(interpretedResponse.bids).to.have.lengthOf(0); - expect(interpretedResponse.fledgeAuctionConfigs).to.have.lengthOf(2); - expect(interpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal({ + expect(interpretedResponse.paapi).to.have.lengthOf(2); + expect(interpretedResponse.paapi[0]).to.deep.equal({ bidId: 'test-bidId', impid: 'test-bidId', config: auctionConfig1, }); - expect(interpretedResponse.fledgeAuctionConfigs[1]).to.deep.equal({ + expect(interpretedResponse.paapi[1]).to.deep.equal({ bidId: 'test-bidId-2', impid: 'test-bidId-2', config: auctionConfig2, @@ -2847,148 +2692,65 @@ describe('The Criteo bidding adapter', function () { hasBidResponseLevelPafData: false, hasBidResponseBidLevelPafData: false, shouldContainsBidMetaPafData: false - }].forEach(testCase => { - const bidPafContentId = 'abcdef'; - const pafTransmission = { - version: '12' - }; - const response = { - slots: [ - { - width: 300, - height: 250, - cpm: 10, - impid: 'adUnitId', - ext: (testCase.hasBidResponseBidLevelPafData ? { - paf: { - content_id: bidPafContentId - } - } : undefined) - } - ], - ext: (testCase.hasBidResponseLevelPafData ? { - paf: { - transmission: pafTransmission - } - } : undefined) - }; - - const request = { - bidRequests: [{ + }].forEach(testCase => + it('should properly forward or not meta paf data', () => { + const bidPafContentId = 'abcdef'; + const pafTransmission = { + version: '12' + }; + const bidRequests = [{ + bidId: 'test-bidId', adUnitCode: 'adUnitId', sizes: [[300, 250]], params: { networkId: 456, } - }] - }; - - const bids = spec.interpretResponse(response, request); + }]; + const response = { + id: 'test-requestId', + seatbid: [{ + seat: 'criteo', + bid: [ + { + id: 'test-bidderId', + impid: 'test-bidId', + w: 728, + h: 90, + ext: { + mediatype: BANNER, + paf: testCase.hasBidResponseBidLevelPafData ? { + content_id: bidPafContentId + } : undefined + } + } + ] + }], + ext: (testCase.hasBidResponseLevelPafData ? { + paf: { + transmission: pafTransmission + } + } : undefined) + }; - expect(bids).to.have.lengthOf(1); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); - const theoreticalBidMetaPafData = { - paf: { - content_id: bidPafContentId, - transmission: pafTransmission - } - }; + expect(bids).to.have.lengthOf(1); - if (testCase.shouldContainsBidMetaPafData) { - expect(bids[0].meta).to.deep.equal(theoreticalBidMetaPafData); - } else { - expect(bids[0].meta).not.to.deep.equal(theoreticalBidMetaPafData); - } - }); - }); - - 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]); - }) - }); - }); + const expectedBidMetaPafData = { + paf: { + content_id: bidPafContentId, + transmission: pafTransmission + } + }; - 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]); + if (testCase.shouldContainsBidMetaPafData) { + expect(bids[0].meta).to.deep.equal(expectedBidMetaPafData); + } else { + expect(bids[0].meta).not.to.deep.equal(expectedBidMetaPafData); + } }) - }); - }); - - describe('tryGetCriteoFastBid', function () { - const VALID_HASH = 'vBeD8Q7GU6lypFbzB07W8hLGj7NL+p7dI9ro2tCxkrmyv0F6stNuoNd75Us33iNKfEoW+cFWypelr6OJPXxki2MXWatRhJuUJZMcK4VBFnxi3Ro+3a0xEfxE4jJm4eGe98iC898M+/YFHfp+fEPEnS6pEyw124ONIFZFrcejpHU='; - const INVALID_HASH = 'invalid'; - const VALID_PUBLISHER_TAG = 'test'; - const INVALID_PUBLISHER_TAG = 'test invalid'; - - const FASTBID_LOCAL_STORAGE_KEY = 'criteo_fast_bid'; - - it('should verify valid hash with valid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').once(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.equals('// Hash: ' + VALID_HASH + '\n' + VALID_PUBLISHER_TAG); - utilsMock.verify(); - }); - - it('should verify valid hash with invalid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + VALID_HASH + '\n' + INVALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify invalid hash with valid publisher tag', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, '// Hash: ' + INVALID_HASH + '\n' + VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').once(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); - - it('should verify missing hash', function () { - localStorage.setItem(FASTBID_LOCAL_STORAGE_KEY, VALID_PUBLISHER_TAG); - - utilsMock.expects('logInfo').withExactArgs('Using Criteo FastBid').never(); - utilsMock.expects('logWarn').withExactArgs('No hash found in FastBid').once(); - utilsMock.expects('logWarn').withExactArgs('Invalid Criteo FastBid found').never(); - - tryGetCriteoFastBid(); - - expect(localStorage.getItem(FASTBID_LOCAL_STORAGE_KEY)).to.be.null; - utilsMock.verify(); - }); + ) }); describe('when pubtag prebid adapter is not available', function () { @@ -2998,12 +2760,24 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 123, - publisherSubId: '123', - nativeCallback: function () { } + publisherSubId: '123' }, nativeParams: { image: { @@ -3029,7 +2803,7 @@ describe('The Criteo bidding adapter', function () { ]; utilsMock.expects('logWarn').withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)').never(); - const request = spec.buildRequests(bidRequestsWithSendId, bidderRequest); + const request = spec.buildRequests(bidRequestsWithSendId, syncAddFPDToBidderRequest(bidderRequest)); utilsMock.verify(); }); @@ -3039,23 +2813,46 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', - sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 123, - publisherSubId: '123', - nativeCallback: function () { } + publisherSubId: '123' }, }, { bidder: 'criteo', adUnitCode: 'bid-456', transactionId: 'transaction-456', - sizes: [[728, 90]], + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] + }, params: { zoneId: 456, - publisherSubId: '456', - nativeCallback: function () { } + publisherSubId: '456' }, }, ]; @@ -3111,132 +2908,9 @@ describe('The Criteo bidding adapter', function () { nativeParamsWithoutSendId.forEach(nativeParams => { let transformedBidRequests = { ...bidRequests }; transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, bidderRequest); + spec.buildRequests(transformedBidRequests, syncAddFPDToBidderRequest(bidderRequest)); }); utilsMock.verify(); }); }); - - describe('when pubtag prebid adapter is available', function () { - it('should forward response to pubtag when calling interpretResponse', () => { - const response = {}; - const request = {}; - - const adapter = { interpretResponse: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('interpretResponse').withExactArgs(response, request).once().returns('ok'); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(request).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - expect(spec.interpretResponse(response, request)).equal('ok'); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onBidWon', () => { - const bid = { auctionId: 123 }; - - const adapter = { handleBidWon: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleBidWon').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onBidWon(bid); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onSetTargeting', () => { - const bid = { auctionId: 123 }; - - const adapter = { handleSetTargeting: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleSetTargeting').withExactArgs(bid).once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(bid.auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onSetTargeting(bid); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should forward bid to pubtag when calling onTimeout', () => { - const timeoutData = [{ auctionId: 123 }]; - - const adapter = { handleBidTimeout: function () { } }; - const adapterMock = sinon.mock(adapter); - adapterMock.expects('handleBidTimeout').once(); - const prebidAdapter = { GetAdapter: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('GetAdapter').withExactArgs(timeoutData[0].auctionId).once().returns(adapter); - - global.Criteo = { - PubTag: { - Adapters: { - Prebid: prebidAdapter - } - } - }; - - spec.onTimeout(timeoutData); - adapterMock.verify(); - prebidAdapterMock.verify(); - }); - - it('should return a POST method with url & data from pubtag', () => { - const bidRequests = {}; - const bidderRequest = {}; - - const prebidAdapter = { buildCdbUrl: function () { }, buildCdbRequest: function () { } }; - const prebidAdapterMock = sinon.mock(prebidAdapter); - prebidAdapterMock.expects('buildCdbUrl').once().returns('cdbUrl'); - prebidAdapterMock.expects('buildCdbRequest').once().returns('cdbRequest'); - - const adapters = { Prebid: function () { } }; - const adaptersMock = sinon.mock(adapters); - adaptersMock.expects('Prebid').withExactArgs(PROFILE_ID_PUBLISHERTAG, ADAPTER_VERSION, bidRequests, bidderRequest, '$prebid.version$', sinon.match.any).once().returns(prebidAdapter); - - global.Criteo = { - PubTag: { - Adapters: adapters - } - }; - - const buildRequestsResult = spec.buildRequests(bidRequests, bidderRequest); - expect(buildRequestsResult.method).equal('POST'); - expect(buildRequestsResult.url).equal('cdbUrl'); - expect(buildRequestsResult.data).equal('cdbRequest'); - - adaptersMock.verify(); - prebidAdapterMock.verify(); - }); - }); }); diff --git a/test/spec/modules/criteoIdSystem_spec.js b/test/spec/modules/criteoIdSystem_spec.js index 975271738e5..eb1f54d7cd2 100644 --- a/test/spec/modules/criteoIdSystem_spec.js +++ b/test/spec/modules/criteoIdSystem_spec.js @@ -2,6 +2,9 @@ import { criteoIdSubmodule, storage } from 'modules/criteoIdSystem.js'; import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; import { server } from '../../mocks/xhr'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const pastDateString = new Date(0).toString() @@ -358,4 +361,20 @@ describe('CriteoId module', function () { expect(callBackSpy.calledOnce).to.be.true; })); + describe('eid', () => { + before(() => { + attachIdSystem(criteoIdSubmodule); + }); + it('criteo', function() { + const userId = { + criteoId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'criteo.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }); diff --git a/test/spec/modules/currency_spec.js b/test/spec/modules/currency_spec.js index e96867f4e84..149a4380036 100644 --- a/test/spec/modules/currency_spec.js +++ b/test/spec/modules/currency_spec.js @@ -3,7 +3,7 @@ import { getCurrencyRates } from 'test/fixtures/fixtures.js'; -import { getGlobal } from 'src/prebidGlobal.js'; +import {getGlobal} from 'src/prebidGlobal.js'; import { setConfig, @@ -13,9 +13,11 @@ import { responseReady } from 'modules/currency.js'; import {createBid} from '../../../src/bidfactory.js'; -import { EVENTS, STATUS, REJECTION_REASON } from '../../../src/constants.js'; +import * as utils from 'src/utils.js'; +import {EVENTS, STATUS, REJECTION_REASON} from '../../../src/constants.js'; import {server} from '../../mocks/xhr.js'; import * as events from 'src/events.js'; +import {requestBidsHook} from '../../../modules/currency.js'; var assert = require('chai').assert; var expect = require('chai').expect; @@ -522,4 +524,54 @@ describe('currency', function () { expect(innerBid.currency).to.equal('CNY'); }); }); + + describe('auctionDelay param', () => { + const continueAuction = sinon.stub(); + let logWarnSpy; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + clock = sinon.useFakeTimers(1046952000000); // 2003-03-06T12:00:00Z + logWarnSpy = sinon.spy(utils, 'logWarn'); + }); + + afterEach(function () { + clock.runAll(); + sandbox.restore(); + clock.restore(); + utils.logWarn.restore(); + continueAuction.resetHistory(); + }); + + it('should delay auction start when auctionDelay set in module config', () => { + setConfig({auctionDelay: 2000, adServerCurrency: 'USD'}); + const reqBidsConfigObj = { + auctionId: '128937' + }; + requestBidsHook(continueAuction, reqBidsConfigObj); + clock.tick(1000); + expect(continueAuction.notCalled).to.be.true; + }); + + it('should start auction when auctionDelay time passed', () => { + setConfig({auctionDelay: 2000, adServerCurrency: 'USD'}); + const reqBidsConfigObj = { + auctionId: '128937' + }; + requestBidsHook(continueAuction, reqBidsConfigObj); + clock.tick(3000); + expect(logWarnSpy.calledOnce).to.equal(true); + expect(continueAuction.calledOnce).to.be.true; + }); + + it('should run auction if rates were fetched before auctionDelay time', () => { + setConfig({auctionDelay: 3000, adServerCurrency: 'USD'}); + const reqBidsConfigObj = { + auctionId: '128937' + }; + fakeCurrencyFileServer.respond(); + requestBidsHook(continueAuction, reqBidsConfigObj); + expect(continueAuction.calledOnce).to.be.true; + }); + }); }); diff --git a/test/spec/modules/cpexIdSystem_spec.js b/test/spec/modules/czechAdIdSystem_spec.js similarity index 54% rename from test/spec/modules/cpexIdSystem_spec.js rename to test/spec/modules/czechAdIdSystem_spec.js index 6e004c9f8ca..19b606b9237 100644 --- a/test/spec/modules/cpexIdSystem_spec.js +++ b/test/spec/modules/czechAdIdSystem_spec.js @@ -1,4 +1,7 @@ -import { czechAdIdSubmodule, storage } from 'modules/czechAdIdSystem.js'; +import {czechAdIdSubmodule, storage} from 'modules/czechAdIdSystem.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('czechAdId module', function () { let getCookieStub; @@ -12,13 +15,13 @@ describe('czechAdId module', function () { getCookieStub.restore(); }); - const cookieTestCasesForEmpty = [undefined, null, ''] + const cookieTestCasesForEmpty = [undefined, null, '']; describe('getId()', function () { it('should return the uid when it exists in cookie', function () { getCookieStub.withArgs('czaid').returns('czechAdIdTest'); const id = czechAdIdSubmodule.getId(); - expect(id).to.be.deep.equal({ id: 'czechAdIdTest' }); + expect(id).to.be.deep.equal({id: 'czechAdIdTest'}); }); cookieTestCasesForEmpty.forEach(testCase => it('should not return the uid when it doesnt exist in cookie', function () { @@ -32,7 +35,22 @@ describe('czechAdId module', function () { it('should return the uid when it exists in cookie', function () { getCookieStub.withArgs('czaid').returns('czechAdIdTest'); const decoded = czechAdIdSubmodule.decode(); - expect(decoded).to.be.deep.equal({ czechAdId: 'czechAdIdTest' }); + expect(decoded).to.be.deep.equal({czechAdId: 'czechAdIdTest'}); + }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(czechAdIdSubmodule); + }); + + it('czechAdId', () => { + const id = 'some-random-id-value'; + const userId = {czechAdId: id}; + const [eid] = createEidsArray(userId); + expect(eid).to.deep.equal({ + source: 'czechadid.cz', + uids: [{id: 'some-random-id-value', atype: 1}] + }); }); }); }); diff --git a/test/spec/modules/dailyhuntBidAdapter_spec.js b/test/spec/modules/dailyhuntBidAdapter_spec.js index f347d6cec5b..ab75264d951 100644 --- a/test/spec/modules/dailyhuntBidAdapter_spec.js +++ b/test/spec/modules/dailyhuntBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('DailyhuntAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); describe('buildRequests', function() { diff --git a/test/spec/modules/dailymotionBidAdapter_spec.js b/test/spec/modules/dailymotionBidAdapter_spec.js index fe9484b2814..2a276a06b15 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -90,13 +90,1005 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, skipmin: 10, startdelay: 0, w: 1280, - h: 720 + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + dmTs: '123456', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + all: { + bidders: '*', + filter: 'include' + } + } + } + }); + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + const { data: reqData } = request; + + expect(request.options.withCredentials).to.eql(false); + expect(request.url).to.equal('https://pb.dmxleo.com'); + + expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; + expect(reqData.bidder_request).to.eql({ + refererInfo: bidderRequestData.refererInfo, + uspConsent: bidderRequestData.uspConsent, + gdprConsent: bidderRequestData.gdprConsent, + gppConsent: bidderRequestData.gppConsent, + }); + expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); + expect(reqData.config.ts).to.eql(bidRequestData[0].params.dmTs); + expect(reqData.coppa).to.be.true; + expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); + expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); + expect(reqData.video_metadata).to.eql({ + description: bidRequestData[0].params.video.description, + iabcat1: ['IAB-1'], + iabcat2: bidRequestData[0].params.video.iabcat2, + id: bidRequestData[0].params.video.id, + lang: bidRequestData[0].params.video.lang, + private: bidRequestData[0].params.video.private, + tags: bidRequestData[0].params.video.tags, + title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, + topics: bidRequestData[0].params.video.topics, + duration: bidRequestData[0].params.video.duration, + livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: [], + siteOrAppContentCat: [], + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerName: bidRequestData[0].params.video.playerName, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, + }); + }); + + it('validates buildRequests with global consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: true + } + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests without gdpr applying', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: false, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent without legitimate interest', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent, with legitimate interest', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent and legitimate interest but publisher forces consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { 573: 1 }, + 7: { 573: 1 }, + 9: { 573: 1 }, + 10: { 573: 1 }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent, no legitimate interest and publisher forces consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { 573: 1 }, + 7: { 573: 1 }, + 9: { 573: 1 }, + 10: { 573: 1 }, + }, + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with detailed consent but publisher full restriction on purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 0, + }, + }, + }, + purpose: { + consents: { + 1: true, + 2: true, + 3: true, + 4: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent but publisher restriction 2 on consent purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 2, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); + }); + + it('validates buildRequests with detailed consent, legitimate interest and publisher restriction on purpose 1', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, }, }, sizes: [[1920, 1080]], @@ -112,9 +1104,14 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', xid: 'x123456', livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, }, }, }]; @@ -128,6 +1125,34 @@ describe('dailymotionBidAdapterTests', () => { apiVersion: 2, consentString: 'xxx', gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 1: { + 573: 1, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, }, gppConsent: { gppString: 'xxx', @@ -156,37 +1181,224 @@ describe('dailymotionBidAdapterTests', () => { () => spec.buildRequests(bidRequestData, bidderRequestData), ); - const { data: reqData } = request; + expect(request.options.withCredentials).to.eql(true); + }); - expect(request.url).to.equal('https://pb.dmxleo.com'); + it('validates buildRequests with detailed consent and legitimate interest but publisher restriction on legitimate interest 2', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + xid: 'x123456', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerVolume: 8, + }, + }, + }]; - expect(reqData.bidder_request).to.eql({ - refererInfo: bidderRequestData.refererInfo, - uspConsent: bidderRequestData.uspConsent, - gdprConsent: bidderRequestData.gdprConsent, - gppConsent: bidderRequestData.gppConsent, - }); - expect(reqData.config.api_key).to.eql(bidRequestData[0].params.apiKey); - expect(reqData.coppa).to.be.true; - expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); - expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); - expect(reqData.video_metadata).to.eql({ - description: bidRequestData[0].params.video.description, - iabcat1: ['IAB-1'], - iabcat2: bidRequestData[0].params.video.iabcat2, - id: bidRequestData[0].params.video.id, - lang: bidRequestData[0].params.video.lang, - private: bidRequestData[0].params.video.private, - tags: bidRequestData[0].params.video.tags, - title: bidRequestData[0].params.video.title, - topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, - duration: bidRequestData[0].params.video.duration, - livestream: !!bidRequestData[0].params.video.livestream, - }); + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + publisher: { + restrictions: { + 2: { + 573: 2, + }, + }, + }, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + legitimateInterests: { + 2: true, + 7: true, + 9: true, + 10: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(true); + }); + + it('validates buildRequests with insufficient consent', () => { + const bidRequestData = [{ + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: 123456, + adUnitCode: 'preroll', + mediaTypes: { + video: { + api: [2, 7], + mimes: ['video/mp4'], + minduration: 5, + maxduration: 30, + playbackmethod: [3], + plcmt: 1, + protocols: [1, 2, 3, 4, 5, 6, 7, 8], + skip: 1, + skipafter: 5, + skipmin: 10, + startdelay: 0, + w: 1280, + h: 720, + }, + }, + sizes: [[1920, 1080]], + params: { + apiKey: 'test_api_key', + video: { + description: 'this is a test video', + duration: 556, + iabcat1: ['IAB-1'], + iabcat2: ['6', '17'], + id: '54321', + lang: 'FR', + private: false, + tags: 'tag_1,tag_2,tag_3', + title: 'test video', + url: 'https://test.com/test', + topics: 'topic_1, topic_2', + livestream: 1, + isCreatedForKids: true, + videoViewsInSession: 2, + autoplay: true, + playerName: 'dailymotion', + playerVolume: 8, + }, + }, + }]; + + const bidderRequestData = { + refererInfo: { + page: 'https://publisher.com', + }, + uspConsent: '1YN-', + gdprConsent: { + apiVersion: 2, + consentString: 'xxx', + gdprApplies: true, + vendorData: { + hasGlobalConsent: false, + purpose: { + consents: { + 1: true, + 3: true, + 4: true, + }, + }, + vendor: { + consents: { + 573: true + } + }, + }, + }, + gppConsent: { + gppString: 'xxx', + applicableSections: [5], + }, + ortb2: { + regs: { + coppa: 1, + }, + site: { + content: { + data: [ + { + name: 'dataprovider.com', + ext: { segtax: 5 }, + segment: [{ id: '200' }], + }, + ], + }, + }, + }, + }; + + const [request] = config.runWithBidder( + 'dailymotion', + () => spec.buildRequests(bidRequestData, bidderRequestData), + ); + + expect(request.options.withCredentials).to.eql(false); }); it('validates buildRequests with content values from App', () => { @@ -200,6 +1412,8 @@ describe('dailymotionBidAdapterTests', () => { mimes: ['video/mp4'], minduration: 5, maxduration: 30, + playbackmethod: [3], + plcmt: 1, protocols: [1, 2, 3, 4, 5, 6, 7, 8], skip: 1, skipafter: 5, @@ -220,9 +1434,15 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', topics: 'topic_1, topic_2', - xid: 'x123456', livestream: 1, + // Test invalid values + isCreatedForKids: 'false', + videoViewsInSession: -1, + autoplay: 'true', + playerName: 'dailymotion', + playerVolume: 12, }, }, }]; @@ -245,6 +1465,13 @@ describe('dailymotionBidAdapterTests', () => { regs: { coppa: 1, }, + device: { + lmt: 1, + ifa: 'xxx', + ext: { + atts: 2, + }, + }, app: { bundle: 'app-bundle', storeurl: 'https://play.google.com/store/apps/details?id=app-bundle', @@ -267,6 +1494,18 @@ describe('dailymotionBidAdapterTests', () => { }, }; + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + iframe: { + bidders: ['dailymotion'], + filter: 'include' + } + } + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestData, bidderRequestData), @@ -276,6 +1515,8 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -286,19 +1527,14 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.true; expect(reqData.appBundle).to.eql(bidderRequestData.ortb2.app.bundle); expect(reqData.appStoreUrl).to.eql(bidderRequestData.ortb2.app.storeurl); + expect(reqData.device.lmt).to.eql(bidderRequestData.ortb2.device.lmt); + expect(reqData.device.ifa).to.eql(bidderRequestData.ortb2.device.ifa); + expect(reqData.device.atts).to.eql(bidderRequestData.ortb2.device.ext.atts); expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.mimes).to.eql(bidRequestData[0].mediaTypes.video.mimes); - expect(reqData.request.mediaTypes.video.minduration).to.eql(bidRequestData[0].mediaTypes.video.minduration); - expect(reqData.request.mediaTypes.video.maxduration).to.eql(bidRequestData[0].mediaTypes.video.maxduration); - expect(reqData.request.mediaTypes.video.protocols).to.eql(bidRequestData[0].mediaTypes.video.protocols); - expect(reqData.request.mediaTypes.video.skip).to.eql(bidRequestData[0].mediaTypes.video.skip); - expect(reqData.request.mediaTypes.video.skipafter).to.eql(bidRequestData[0].mediaTypes.video.skipafter); - expect(reqData.request.mediaTypes.video.skipmin).to.eql(bidRequestData[0].mediaTypes.video.skipmin); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); - expect(reqData.request.mediaTypes.video.w).to.eql(bidRequestData[0].mediaTypes.video.w); - expect(reqData.request.mediaTypes.video.h).to.eql(bidRequestData[0].mediaTypes.video.h); + + expect(reqData.request.mediaTypes.video).to.eql(bidRequestData[0].mediaTypes.video); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-1'], @@ -308,11 +1544,20 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidRequestData[0].params.video.tags, title: bidRequestData[0].params.video.title, + url: bidRequestData[0].params.video.url, topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, // Overriden through bidder params duration: bidderRequestData.ortb2.app.content.len, livestream: !!bidRequestData[0].params.video.livestream, + isCreatedForKids: null, + context: { + siteOrAppCat: [], + siteOrAppContentCat: [], + videoViewsInSession: null, + autoplay: null, + playerName: 'dailymotion', + playerVolume: null, + }, }); }); @@ -336,7 +1581,11 @@ describe('dailymotionBidAdapterTests', () => { private: false, title: 'test video', topics: 'topic_1, topic_2', - xid: 'x123456', + isCreatedForKids: false, + videoViewsInSession: 10, + autoplay: false, + playerName: 'dailymotion', + playerVolume: 0, }, }, }]; @@ -358,11 +1607,13 @@ describe('dailymotionBidAdapterTests', () => { coppa: 0, }, site: { + cat: ['IAB-1'], content: { id: '54321', language: 'FR', keywords: 'tag_1,tag_2,tag_3', title: 'test video', + url: 'https://test.com/test', livestream: 1, cat: ['IAB-2'], data: [ @@ -410,6 +1661,22 @@ describe('dailymotionBidAdapterTests', () => { }, }; + config.setConfig({ + userSync: { + syncEnabled: true, + filterSettings: { + image: { + bidders: ['dailymotion'], + filter: 'include' + }, + iframe: { + bidders: ['dailymotion'], + filter: 'exclude', + }, + } + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestData, bidderRequestData), @@ -419,6 +1686,8 @@ describe('dailymotionBidAdapterTests', () => { expect(request.url).to.equal('https://pb.dmxleo.com'); + expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.true; expect(reqData.bidder_request).to.eql({ refererInfo: bidderRequestData.refererInfo, uspConsent: bidderRequestData.uspConsent, @@ -432,9 +1701,22 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.coppa).to.be.false; expect(reqData.request.auctionId).to.eql(bidRequestData[0].auctionId); expect(reqData.request.bidId).to.eql(bidRequestData[0].bidId); - expect(reqData.request.mediaTypes.video.api).to.eql(bidRequestData[0].mediaTypes.video.api); - expect(reqData.request.mediaTypes.video.playerSize).to.eql(bidRequestData[0].mediaTypes.video.playerSize); - expect(reqData.request.mediaTypes.video.startdelay).to.eql(bidRequestData[0].mediaTypes.video.startdelay); + + expect(reqData.request.mediaTypes.video).to.eql({ + ...bidRequestData[0].mediaTypes.video, + mimes: [], + minduration: 0, + maxduration: 0, + playbackmethod: [], + plcmt: undefined, + protocols: [], + skip: 0, + skipafter: 0, + skipmin: 0, + w: 0, + h: 0, + }); + expect(reqData.video_metadata).to.eql({ description: bidRequestData[0].params.video.description, iabcat1: ['IAB-2'], @@ -444,10 +1726,19 @@ describe('dailymotionBidAdapterTests', () => { private: bidRequestData[0].params.video.private, tags: bidderRequestData.ortb2.site.content.keywords, title: bidderRequestData.ortb2.site.content.title, + url: bidderRequestData.ortb2.site.content.url, topics: bidRequestData[0].params.video.topics, - xid: bidRequestData[0].params.video.xid, duration: bidRequestData[0].params.video.duration, livestream: !!bidderRequestData.ortb2.site.content.livestream, + isCreatedForKids: bidRequestData[0].params.video.isCreatedForKids, + context: { + siteOrAppCat: bidderRequestData.ortb2.site.cat, + siteOrAppContentCat: bidderRequestData.ortb2.site.content.cat, + videoViewsInSession: bidRequestData[0].params.video.videoViewsInSession, + autoplay: bidRequestData[0].params.video.autoplay, + playerName: bidRequestData[0].params.video.playerName, + playerVolume: bidRequestData[0].params.video.playerVolume, + }, }); }); @@ -458,6 +1749,12 @@ describe('dailymotionBidAdapterTests', () => { }, }]; + config.setConfig({ + userSync: { + syncEnabled: false, + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestDataWithApi, {}), @@ -470,6 +1767,8 @@ describe('dailymotionBidAdapterTests', () => { expect(reqData.config.api_key).to.eql(bidRequestDataWithApi[0].params.apiKey); expect(reqData.coppa).to.be.false; + expect(reqData.pbv).to.eql('$prebid.version$'); + expect(reqData.userSyncEnabled).to.be.false; expect(reqData.bidder_request).to.eql({ gdprConsent: { apiVersion: 1, @@ -496,11 +1795,13 @@ describe('dailymotionBidAdapterTests', () => { mimes: [], minduration: 0, maxduration: 0, + playbackmethod: [], + plcmt: undefined, protocols: [], skip: 0, skipafter: 0, skipmin: 0, - startdelay: 0, + startdelay: undefined, w: 0, h: 0, }, @@ -518,9 +1819,18 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: '', title: '', + url: '', topics: '', - xid: '', livestream: false, + isCreatedForKids: null, + context: { + siteOrAppCat: [], + siteOrAppContentCat: [], + videoViewsInSession: null, + autoplay: null, + playerName: '', + playerVolume: null, + }, }); }); @@ -557,4 +1867,87 @@ describe('dailymotionBidAdapterTests', () => { expect(spec.interpretResponse(undefined)).to.have.lengthOf(0); }); + + it('validates getUserSyncs', () => { + // Nothing sent in getUserSyncs + expect(config.runWithBidder('dailymotion', () => spec.getUserSyncs())).to.eql([]); + + // No server response + { + const responses = []; + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // No permissions + { + const responses = [{ body: { userSyncs: [{ url: 'https://usersyncurl.com', type: 'image' }] } }]; + const syncOptions = { iframeEnabled: false, pixelEnabled: false }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Has permissions but no userSyncs urls + { + const responses = [{}]; + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([]); + } + + // Return userSyncs urls for pixels + { + const responses = [{ + body: { + userSyncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + } + }]; + + const syncOptions = { iframeEnabled: false, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'image', url: 'https://usersyncurl.com' }, + { type: 'image', url: 'https://usersyncurl2.com' }, + ]); + } + + // Return userSyncs urls for iframes + { + const responses = [{ + body: { + userSyncs: [ + { url: 'https://usersyncurl.com', type: 'image' }, + { url: 'https://usersyncurl2.com', type: 'image' }, + { url: 'https://usersyncurl3.com', type: 'iframe' } + ], + } + }]; + + const syncOptions = { iframeEnabled: true, pixelEnabled: true }; + + expect(config.runWithBidder( + 'dailymotion', + () => spec.getUserSyncs(syncOptions, responses), + )).to.eql([ + { type: 'iframe', url: 'https://usersyncurl3.com' }, + ]); + } + }); }); diff --git a/test/spec/modules/datawrkzBidAdapter_spec.js b/test/spec/modules/datawrkzBidAdapter_spec.js index 5524e318600..e78d2f68d91 100644 --- a/test/spec/modules/datawrkzBidAdapter_spec.js +++ b/test/spec/modules/datawrkzBidAdapter_spec.js @@ -36,26 +36,26 @@ describe('datawrkzAdapterTests', function () { }); it('should return false when params not found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required site_id param not found', function () { - let bid = Object.assign({}, bid); - bid.params = {'bidfloor': '1.0'} - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {'bidfloor': '1.0'} + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when adunit is adpod video', function () { - let bid = Object.assign({}, bid); - bid.params = {'bidfloor': '1.0', 'site_id': SITE_ID}; - bid.mediaTypes = { + let invalidBid = Object.assign({}, bid); + invalidBid.params = {'bidfloor': '1.0', 'site_id': SITE_ID}; + invalidBid.mediaTypes = { 'video': { 'context': 'adpod' } } - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/debugging_mod_spec.js b/test/spec/modules/debugging_mod_spec.js index ab99ba2aa0c..438c70fae64 100644 --- a/test/spec/modules/debugging_mod_spec.js +++ b/test/spec/modules/debugging_mod_spec.js @@ -37,7 +37,12 @@ describe('bid interceptor', () => { describe('serializeConfig', () => { Object.entries({ regexes: /pat/, - functions: () => ({}) + functions: () => ({}), + 'undefined': undefined, + date: new Date(), + symbol: Symbol('test'), + map: new Map(), + set: new Set(), }).forEach(([test, arg]) => { it(`should filter out ${test}`, () => { const valid = [{key1: 'value'}, {key2: 'value'}]; @@ -167,8 +172,8 @@ describe('bid interceptor', () => { describe('paapi', () => { it('should accept literals', () => { const mockConfig = [ - {paapi: 1}, - {paapi: 2} + {config: {paapi: 1}}, + {config: {paapi: 2}} ] const paapi = matchingRule({paapi: mockConfig}).paapi({}); expect(paapi).to.eql(mockConfig); @@ -179,6 +184,30 @@ describe('bid interceptor', () => { const args = [{}, {}, {}]; matchingRule({paapi: paapiDef}).paapi(...args); expect(paapiDef.calledOnceWith(...args.map(sinon.match.same))).to.be.true; + }); + + Object.entries({ + 'literal': (cfg) => [cfg], + 'function': (cfg) => () => [cfg] + }).forEach(([t, makeConfigs]) => { + describe(`when paapi is defined as a ${t}`, () => { + it('should wrap top-level configs in "config"', () => { + const cfg = {decisionLogicURL: 'example'}; + expect(matchingRule({paapi: makeConfigs(cfg)}).paapi({})).to.eql([{ + config: cfg + }]) + }); + + Object.entries({ + 'config': {config: 1}, + 'igb': {igb: 1}, + 'config and igb': {config: 1, igb: 2} + }).forEach(([t, cfg]) => { + it(`should not wrap configs that define top-level ${t}`, () => { + expect(matchingRule({paapi: makeConfigs(cfg)}).paapi({})).to.eql([cfg]); + }) + }) + }) }) }) @@ -274,8 +303,8 @@ describe('bid interceptor', () => { it('should call addPaapiConfigs when provided', () => { const mockPaapiConfigs = [ - {paapi: 1}, - {paapi: 2} + {config: {paapi: 1}}, + {config: {paapi: 2}} ] setRules({ when: {id: 2}, @@ -325,17 +354,30 @@ describe('Debugging config', () => { }); describe('bidderBidInterceptor', () => { - let next, interceptBids, onCompletion, interceptResult, done, addBid; + let next, interceptBids, onCompletion, interceptResult, done, addBid, wrapCallback, addPaapiConfig, wrapped; - function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, wrapCallback = {}, cbs = {}} = {}) { + function interceptorArgs({spec = {}, bids = [], bidRequest = {}, ajax = {}, cbs = {}} = {}) { return [next, interceptBids, spec, bids, bidRequest, ajax, wrapCallback, Object.assign({onCompletion}, cbs)]; } beforeEach(() => { next = sinon.spy(); + wrapped = false; + wrapCallback = sinon.stub().callsFake(cb => { + if (cb == null) return cb; + return function () { + wrapped = true; + try { + return cb.apply(this, arguments) + } finally { + wrapped = false; + } + } + }); interceptBids = sinon.stub().callsFake((opts) => { done = opts.done; addBid = opts.addBid; + addPaapiConfig = opts.addPaapiConfig; return interceptResult; }); onCompletion = sinon.spy(); @@ -343,13 +385,26 @@ describe('bidderBidInterceptor', () => { }); it('should pass to interceptBid an addBid that triggers onBid', () => { - const onBid = sinon.spy(); + const onBid = sinon.stub().callsFake(() => { + expect(wrapped).to.be.true; + }); bidderBidInterceptor(...interceptorArgs({cbs: {onBid}})); - const bid = {}; + const bid = { + bidder: 'bidder' + }; addBid(bid); expect(onBid.calledWith(sinon.match.same(bid))).to.be.true; }); + it('should pass addPaapiConfig that triggers onPaapi', () => { + const onPaapi = sinon.stub().callsFake(() => { + expect(wrapped).to.be.true; + }); + bidderBidInterceptor(...interceptorArgs({cbs: {onPaapi}})); + addPaapiConfig({paapi: 'config'}, {bidId: 'bidId'}); + sinon.assert.calledWith(onPaapi, {paapi: 'config', bidId: 'bidId'}) + }) + describe('with no remaining bids', () => { it('should pass a done callback that triggers onCompletion', () => { bidderBidInterceptor(...interceptorArgs()); @@ -358,6 +413,12 @@ describe('bidderBidInterceptor', () => { expect(onCompletion.calledOnce).to.be.true; }); + it('should call onResponse', () => { + const onResponse = sinon.stub(); + bidderBidInterceptor(...interceptorArgs({cbs: {onResponse}})); + sinon.assert.called(onResponse); + }) + it('should not call next()', () => { bidderBidInterceptor(...interceptorArgs()); expect(next.called).to.be.false; diff --git a/test/spec/modules/deepintentBidAdapter_spec.js b/test/spec/modules/deepintentBidAdapter_spec.js index 644e9255789..b64c29d6800 100644 --- a/test/spec/modules/deepintentBidAdapter_spec.js +++ b/test/spec/modules/deepintentBidAdapter_spec.js @@ -252,6 +252,22 @@ describe('Deepintent adapter', function () { expect(data.imp[0].displaymanager).to.equal('di_prebid'); expect(data.imp[0].displaymanagerver).to.equal('1.0.0'); }); + it('bid request check: bidfloor check', function() { + const requestClone = utils.deepClone(request); + let bRequest = spec.buildRequests(requestClone); + let data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.not.exist; + + requestClone[0].params.bidfloor = 0; + bRequest = spec.buildRequests(requestClone); + data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.equal(0); + + requestClone[0].params.bidfloor = 1.2; + bRequest = spec.buildRequests(requestClone); + data = JSON.parse(bRequest.data); + expect(data.imp[0].bidfloor).to.equal(1.2); + }); it('bid request check: user object check', function () { let bRequest = spec.buildRequests(request); let data = JSON.parse(bRequest.data); diff --git a/test/spec/modules/deepintentDpesIdsystem_spec.js b/test/spec/modules/deepintentDpesIdsystem_spec.js index 3bd1e71d8f6..252cb2f414c 100644 --- a/test/spec/modules/deepintentDpesIdsystem_spec.js +++ b/test/spec/modules/deepintentDpesIdsystem_spec.js @@ -1,5 +1,7 @@ import { expect } from 'chai'; import { deepintentDpesSubmodule } from 'modules/deepintentDpesIdSystem.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const DI_COOKIE_OBJECT = {id: '2cf40748c4f7f60d343336e08f80dc99'}; const DI_UPDATED_STORAGE = '2cf40748c4f7f60d343336e08f80dc99'; @@ -59,4 +61,20 @@ describe('Deepintent DPES System', () => { expect(deepintentDpesSubmodule.eids.deepintentId.getValue(DI_UPDATED_STORAGE)).to.be.eq(DI_UPDATED_STORAGE); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(deepintentDpesSubmodule); + }) + 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}] + }); + }); + }) }); diff --git a/test/spec/modules/dexertoBidAdapter_spec.js b/test/spec/modules/dexertoBidAdapter_spec.js new file mode 100644 index 00000000000..419fcbc9dbe --- /dev/null +++ b/test/spec/modules/dexertoBidAdapter_spec.js @@ -0,0 +1,199 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/dexertoBidAdapter'; +import * as utils from '../../../src/utils.js'; + +describe('dexerto adapter', function () { + let request; + let bannerResponse, invalidResponse; + + beforeEach(function () { + request = [ + { + bidder: 'dexerto', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 110003, + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + } + ]; + bannerResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 0.5, + 'adid': '3424', + 'adm': "
", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.audiencelogy.com/1_3424_1.jpg', + 'cid': '410011', + 'crid': '410011', + 'w': 300, + 'h': 250, + 'cat': ['IAB1-15'] + }], + 'seat': 'audiencelogy', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1256' + } + }; + invalidResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 0.5, + 'adid': '3424', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.audiencelogy.com/1_3424_1.jpg', + 'cid': '410011', + 'crid': '410011', + 'w': 300, + 'h': 250, + 'cat': ['IAB1-15'] + }], + 'seat': 'audiencelogy', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1256' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'dexerto', + params: { + placement_id: 110003 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'dexerto', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(request); + expect(_Request.url).to.equal('https://rtb.dexerto.media/hb/dexerto'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(request[0].bidId); + expect(data[0].placementId).to.equal(110003); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(request, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate response ', function () { + it('Validate bid response : valid bid response', function () { + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(request); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/dfpAdServerVideo_spec.js b/test/spec/modules/dfpAdServerVideo_spec.js index 39713c2b51a..092cd1ff0f3 100644 --- a/test/spec/modules/dfpAdServerVideo_spec.js +++ b/test/spec/modules/dfpAdServerVideo_spec.js @@ -1,16 +1,15 @@ import {expect} from 'chai'; import parse from 'url-parse'; -import {buildAdpodVideoUrl, buildDfpVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; +import {buildDfpVideoUrl, dep} from 'modules/dfpAdServerVideo.js'; import AD_UNIT from 'test/fixtures/video/adUnit.json'; import * as utils from 'src/utils.js'; import {deepClone} 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'; +import {gdprDataHandler} from 'src/adapterManager.js'; + import * as adServer from 'src/adserver.js'; import {hook} from '../../../src/hook.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -707,252 +706,4 @@ describe('The DFP video support module', function () { expect(customParams).to.have.property('other_key', 'other_value'); expect(customParams).to.have.property('hb_rand', 'random'); }); - - describe('adpod unit tests', function () { - let amStub; - let amGetAdUnitsStub; - - before(function () { - let adUnits = [{ - code: 'adUnitCode-1', - mediaTypes: { - video: { - context: 'adpod', - playerSize: [640, 480], - adPodDurationSec: 60, - durationRangeSec: [15, 30], - requireExactDuration: true - } - }, - bids: [ - { - bidder: 'appnexus', - params: { - placementId: 14542875, - } - } - ] - }]; - - amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); - amGetAdUnitsStub.returns(adUnits); - amStub = sinon.stub(auctionManager, 'getBidsReceived'); - }); - - beforeEach(function () { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: false - } - }); - }) - - afterEach(function() { - config.resetConfig(); - }); - - after(function () { - amGetAdUnitsStub.restore(); - amStub.restore(); - }); - - 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', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - 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', '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('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(); - } - }); - - it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: false, - } - }); - function getBids() { - let bids = [ - createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), - ]; - bids.forEach((bid) => { - delete bid.meta; - }); - return bids; - } - amStub.returns(getBids()); - let url; - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - function handleResponse(err, masterTag) { - if (err) { - return; - } - url = parse(masterTag); - expect(url.protocol).to.equal('https:'); - expect(url.host).to.equal('securepubads.g.doubleclick.net'); - - const queryParams = utils.parseQS(url.query); - expect(queryParams).to.have.property('correlator'); - expect(queryParams).to.have.property('description_url', 'someUrl.com'); - 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('sz', '640x480'); - expect(queryParams).to.have.property('unviewed_position_start', '1'); - expect(queryParams).to.have.property('url'); - expect(queryParams).to.have.property('cust_params'); - - 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', '10.00_15s,15.00_15s,15.00_30s'); - } - }); - - it('should handle error when cache fails', function() { - config.setConfig({ - adpod: { - brandCategoryExclusion: true, - deferCaching: true - } - }); - amStub.returns(getBidsReceived()); - - parse(buildAdpodVideoUrl({ - code: 'adUnitCode-1', - callback: handleResponse, - params: { - 'iu': 'my/adUnit', - 'description_url': 'someUrl.com', - } - })); - - server.requests[0].respond(503, { - 'Content-Type': 'plain/text', - }, 'The server could not save anything at the moment.'); - - function handleResponse(err, masterTag) { - expect(masterTag).to.be.null; - expect(err).to.be.an('error'); - } - }); - }) }); - -function getBidsReceived() { - return [ - createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), - createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), - createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), - ] -} - -function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { - return { - 'bidderCode': 'appnexus', - 'width': 640, - 'height': 360, - 'statusMessage': 'Bid available', - 'adId': '28f24ced14586c', - 'mediaType': 'video', - 'source': 'client', - 'requestId': '28f24ced14586c', - 'cpm': cpm, - 'creativeId': 97517771, - 'currency': 'USD', - 'netRevenue': true, - 'ttl': 3600, - 'adUnitCode': adUnitCode, - 'video': { - 'context': 'adpod', - 'durationBucket': durationBucket - }, - 'appnexus': { - 'buyerMemberId': 9325 - }, - 'vastUrl': 'http://some-vast-url.com', - 'vastImpUrl': 'http://some-vast-imp-url.com', - 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', - 'responseTimestamp': 1548442460888, - 'requestTimestamp': 1548442460827, - 'bidder': 'appnexus', - 'timeToRespond': 61, - 'pbLg': '5.00', - 'pbMg': '5.00', - 'pbHg': '5.00', - 'pbAg': '5.00', - 'pbDg': '5.00', - 'pbCg': '', - 'size': '640x360', - 'adserverTargeting': { - 'hb_bidder': 'appnexus', - 'hb_adid': '28f24ced14586c', - 'hb_pb': hbpb, - 'hb_size': '640x360', - 'hb_source': 'client', - 'hb_format': 'video', - 'hb_pb_cat_dur': priceIndustryDuration, - 'hb_cache_id': uuid - }, - 'customCacheKey': `${priceIndustryDuration}_${uuid}`, - 'meta': { - 'primaryCatId': 'iab-1', - 'adServerCatId': label - }, - 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' - } -} diff --git a/test/spec/modules/dfpAdpod_spec.js b/test/spec/modules/dfpAdpod_spec.js new file mode 100644 index 00000000000..33d724dac26 --- /dev/null +++ b/test/spec/modules/dfpAdpod_spec.js @@ -0,0 +1,257 @@ +import {auctionManager} from '../../../src/auctionManager.js'; +import {config} from '../../../src/config.js'; +import {gdprDataHandler, uspDataHandler} from '../../../src/consentHandler.js'; +import parse from 'url-parse'; +import {buildAdpodVideoUrl} from '../../../modules/dfpAdpod.js'; +import {expect} from 'chai/index.js'; +import * as utils from '../../../src/utils.js'; +import {server} from '../../mocks/xhr.js'; +import * as adpod from 'modules/adpod.js'; + +describe('dfpAdpod', function () { + let amStub; + let amGetAdUnitsStub; + + before(function () { + let adUnits = [{ + code: 'adUnitCode-1', + mediaTypes: { + video: { + context: 'adpod', + playerSize: [640, 480], + adPodDurationSec: 60, + durationRangeSec: [15, 30], + requireExactDuration: true + } + }, + bids: [ + { + bidder: 'appnexus', + params: { + placementId: 14542875, + } + } + ] + }]; + + amGetAdUnitsStub = sinon.stub(auctionManager, 'getAdUnits'); + amGetAdUnitsStub.returns(adUnits); + amStub = sinon.stub(auctionManager, 'getBidsReceived'); + }); + + beforeEach(function () { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: false + } + }); + }) + + afterEach(function() { + config.resetConfig(); + }); + + after(function () { + amGetAdUnitsStub.restore(); + amStub.restore(); + }); + + function getBidsReceived() { + return [ + createBid(10, 'adUnitCode-1', 15, '10.00_395_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_395_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_406_30s', '123', '406', '25.00'), + ] + } + + function createBid(cpm, adUnitCode, durationBucket, priceIndustryDuration, uuid, label, hbpb) { + return { + 'bidderCode': 'appnexus', + 'width': 640, + 'height': 360, + 'statusMessage': 'Bid available', + 'adId': '28f24ced14586c', + 'mediaType': 'video', + 'source': 'client', + 'requestId': '28f24ced14586c', + 'cpm': cpm, + 'creativeId': 97517771, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 3600, + 'adUnitCode': adUnitCode, + 'video': { + 'context': 'adpod', + 'durationBucket': durationBucket + }, + 'appnexus': { + 'buyerMemberId': 9325 + }, + 'vastUrl': 'http://some-vast-url.com', + 'vastImpUrl': 'http://some-vast-imp-url.com', + 'auctionId': 'ec266b31-d652-49c5-8295-e83fafe5532b', + 'responseTimestamp': 1548442460888, + 'requestTimestamp': 1548442460827, + 'bidder': 'appnexus', + 'timeToRespond': 61, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '640x360', + 'adserverTargeting': { + 'hb_bidder': 'appnexus', + 'hb_adid': '28f24ced14586c', + 'hb_pb': hbpb, + 'hb_size': '640x360', + 'hb_source': 'client', + 'hb_format': 'video', + 'hb_pb_cat_dur': priceIndustryDuration, + 'hb_cache_id': uuid + }, + 'customCacheKey': `${priceIndustryDuration}_${uuid}`, + 'meta': { + 'primaryCatId': 'iab-1', + 'adServerCatId': label + }, + 'videoCacheKey': '4cf395af-8fee-4960-af0e-88d44e399f14' + } + } + + 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', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = utils.parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + 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', '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('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(); + } + }); + + it('should return masterTag url with correct custom params when brandCategoryExclusion is false', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: false, + } + }); + function getBids() { + let bids = [ + createBid(10, 'adUnitCode-1', 15, '10.00_15s', '123', '395', '10.00'), + createBid(15, 'adUnitCode-1', 15, '15.00_15s', '123', '395', '15.00'), + createBid(25, 'adUnitCode-1', 30, '15.00_30s', '123', '406', '25.00'), + ]; + bids.forEach((bid) => { + delete bid.meta; + }); + return bids; + } + amStub.returns(getBids()); + let url; + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + function handleResponse(err, masterTag) { + if (err) { + return; + } + url = parse(masterTag); + expect(url.protocol).to.equal('https:'); + expect(url.host).to.equal('securepubads.g.doubleclick.net'); + + const queryParams = utils.parseQS(url.query); + expect(queryParams).to.have.property('correlator'); + expect(queryParams).to.have.property('description_url', 'someUrl.com'); + 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('sz', '640x480'); + expect(queryParams).to.have.property('unviewed_position_start', '1'); + expect(queryParams).to.have.property('url'); + expect(queryParams).to.have.property('cust_params'); + + 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', '10.00_15s,15.00_15s,15.00_30s'); + } + }); + + it('should handle error when cache fails', function() { + config.setConfig({ + adpod: { + brandCategoryExclusion: true, + deferCaching: true + } + }); + amStub.returns(getBidsReceived()); + + parse(buildAdpodVideoUrl({ + code: 'adUnitCode-1', + callback: handleResponse, + params: { + 'iu': 'my/adUnit', + 'description_url': 'someUrl.com', + } + })); + + server.requests[0].respond(503, { + 'Content-Type': 'plain/text', + }, 'The server could not save anything at the moment.'); + + function handleResponse(err, masterTag) { + expect(masterTag).to.be.null; + expect(err).to.be.an('error'); + } + }); +}) diff --git a/test/spec/modules/dianomiBidAdapter_spec.js b/test/spec/modules/dianomiBidAdapter_spec.js index 0838762d750..b1ba5f60540 100644 --- a/test/spec/modules/dianomiBidAdapter_spec.js +++ b/test/spec/modules/dianomiBidAdapter_spec.js @@ -250,23 +250,20 @@ describe('Dianomi adapter', () => { { bidId: 'bidId', params: { smartadId: 1234 }, - userIdAsEids: createEidsArray({ - tdid: 'TTD_ID_FROM_USER_ID_MODULE', - pubcid: 'pubCommonId_FROM_USER_ID_MODULE', - }), + userIdAsEids: [ + { + source: 'adserver.org', + uids: [{ id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } }], + }, + { source: 'pubcid.org', uids: [{ id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 }] }, + ], }, ]; let request = JSON.parse( spec.buildRequests(validBidRequests, { refererInfo: { page: 'page' } }).data ); - assert.deepEqual(request.user.ext.eids, [ - { - source: 'adserver.org', - uids: [{ id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } }], - }, - { source: 'pubcid.org', uids: [{ id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 }] }, - ]); + assert.deepEqual(request.user.ext.eids, validBidRequests[0].userIdAsEids); }); it('should send currency if defined', () => { diff --git a/test/spec/modules/digitalMatterBidAdapter_spec.js b/test/spec/modules/digitalMatterBidAdapter_spec.js new file mode 100644 index 00000000000..87056588cd9 --- /dev/null +++ b/test/spec/modules/digitalMatterBidAdapter_spec.js @@ -0,0 +1,265 @@ +import {assert, expect} from 'chai'; +import {spec} from 'modules/digitalMatterBidAdapter'; +import {config} from '../../../src/config'; +import {deepClone} from '../../../src/utils'; + +const bid = { + 'adUnitCode': 'adUnitCode', + 'bidId': 'bidId', + 'bidder': 'digitalMatter', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250], [300, 600]] + } + }, + 'params': { + 'accountId': '1_demo_1', + 'siteId': '1-demo-1' + } +}; +const bidderRequest = { + ortb2: { + source: { + tid: 'tid-string' + }, + regs: { + ext: { + gdpr: 1 + } + }, + site: { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' + }, + device: { + w: 100, + h: 100, + dnt: 0, + ua: navigator.userAgent, + language: 'en' + } + } +}; + +describe('Digital Matter BidAdapter', function () { + describe('isBidRequestValid', function () { + it('should return true when all required params found', function () { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + + it('should return false when required params are not passed', function () { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + + it('should return false when media type banner is missing', function () { + let invalidBid = deepClone(bid); + delete invalidBid.mediaTypes.banner; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + it('should send request with correct structure', function () { + let request = spec.buildRequests([bid], bidderRequest); + + assert.equal(request.method, 'POST'); + assert.equal(request.url, 'https://adx.digitalmatter.services/openrtb2/auction'); + assert.equal(request.options, undefined); + assert.ok(request.data); + }); + + it('should have default request structure', function () { + let keys = 'tid,site,device,imp,test,ext'.split(','); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + let data = Object.keys(request); + + assert.deepEqual(keys, data); + }); + + it('should send info about device', function () { + config.setConfig({ + device: {w: 1920, h: 1080} + }); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.equal(request.device.ua, navigator.userAgent); + assert.equal(request.device.w, 100); + assert.equal(request.device.h, 100); + }); + + it('should send info about the site', function () { + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.site, { + domain: 'publisher.domain.com', + publisher: { + domain: 'publisher.domain.com' + }, + page: 'https://publisher.domain.com/test.html' + }); + }); + + it('should send currency if defined', function () { + config.setConfig({currency: {adServerCurrency: 'EUR'}}); + let request = JSON.parse(spec.buildRequests([bid], bidderRequest).data); + + assert.deepEqual(request.cur, [{adServerCurrency: 'EUR'}]); + }); + + it('should pass supply chain object', function () { + let validBidRequests = { + ...bid, + schain: { + validation: 'strict', + config: { + ver: '1.0' + } + } + }; + + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + assert.deepEqual(request.source.ext.schain, { + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should pass extended ids if exists', function () { + let validBidRequests = { + ...bid, + userIdAsEids: [ + { + source: 'adserver.org', + uids: [ + { + id: 'TTD_ID_FROM_USER_ID_MODULE', + atype: 1, + ext: { + rtiPartner: 'TDID' + } + } + ] + } + ] + }; + + let request = JSON.parse(spec.buildRequests([validBidRequests], bidderRequest).data); + + assert.deepEqual(request.user.ext.eids, validBidRequests.userIdAsEids); + }); + + it('should pass gdpr consent data if gdprApplies', function () { + let consentedBidderRequest = { + ...bidderRequest, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString' + } + }; + + let request = JSON.parse(spec.buildRequests([bid], consentedBidderRequest).data); + assert.equal(request.user.ext.consent, consentedBidderRequest.gdprConsent.consentString); + assert.equal(request.regs.ext.gdpr, consentedBidderRequest.gdprConsent.gdprApplies); + assert.equal(typeof request.regs.ext.gdpr, 'number'); + }); + }); + + describe('interpretResponse', function () { + it('should return empty array if no body in response', function () { + assert.ok(spec.interpretResponse([])); + }); + + it('should return array with bids if response not empty', function () { + const firstResponse = { + id: 'id_1', + impid: 'impId_1', + bidid: 'bidId_1', + adunitcode: 'adUnitCode_1', + cpm: 0.10, + ad: '

ad', + adomain: [ + 'advertiser.org' + ], + width: 970, + height: 250, + creativeid: 'creativeId_1', + meta: { + advertiserDomains: [ + 'advertiser.org' + ] + } + }; + const secondResponse = { + 'id': 'id_2', + 'impid': 'impId_2', + 'bidid': 'bidId_2', + 'adunitcode': 'adUnitCode_2', + 'cpm': 0.11, + 'ad': '

ad', + 'adomain': [ + 'advertiser.org' + ], + 'width': 970, + 'height': 250, + 'creativeid': 'creativeId_2', + 'meta': { + 'advertiserDomains': [ + 'advertiser.org' + ] + } + }; + const currency = 'EUR'; + + const bids = spec.interpretResponse({ + body: { + id: 'randomId', + cur: currency, + bids: [ + firstResponse, + secondResponse + ] + } + }); + + assert.ok(bids); + assert.deepEqual(bids[0].requestId, firstResponse.bidid); + assert.deepEqual(bids[0].cpm, firstResponse.cpm); + assert.deepEqual(bids[0].creativeId, firstResponse.creativeid); + assert.deepEqual(bids[0].ttl, 300); + assert.deepEqual(bids[0].netRevenue, true); + assert.deepEqual(bids[0].currency, currency); + assert.deepEqual(bids[0].width, firstResponse.width); + assert.deepEqual(bids[0].height, firstResponse.height); + assert.deepEqual(bids[0].dealId, undefined); + assert.deepEqual(bids[0].meta.advertiserDomains, [ 'advertiser.org' ]); + + assert.deepEqual(bids[1].requestId, secondResponse.bidid); + assert.deepEqual(bids[1].cpm, secondResponse.cpm); + assert.deepEqual(bids[1].creativeId, secondResponse.creativeid); + assert.deepEqual(bids[1].ttl, 300); + assert.deepEqual(bids[1].netRevenue, true); + assert.deepEqual(bids[1].currency, currency); + assert.deepEqual(bids[1].width, secondResponse.width); + assert.deepEqual(bids[1].height, secondResponse.height); + assert.deepEqual(bids[1].dealId, undefined); + assert.deepEqual(bids[1].meta.advertiserDomains, [ 'advertiser.org' ]); + }); + }); + + describe('getUserSyncs', function () { + it('handle empty array (e.g. timeout)', function () { + const syncs = spec.getUserSyncs({ pixelEnabled: true, iframeEnabled: true }, []); + expect(syncs).to.deep.equal([]); + }); + }); +}); diff --git a/test/spec/modules/discoveryBidAdapter_spec.js b/test/spec/modules/discoveryBidAdapter_spec.js index 42cc6ff68eb..e65243fd15a 100644 --- a/test/spec/modules/discoveryBidAdapter_spec.js +++ b/test/spec/modules/discoveryBidAdapter_spec.js @@ -3,16 +3,14 @@ import { spec, getPmgUID, storage, - getPageTitle, - getPageDescription, - getPageKeywords, - getConnectionDownLink, THIRD_PARTY_COOKIE_ORIGIN, COOKIE_KEY_MGUID, - getCurrentTimeToUTCString, - buildUTMTagData + getCookieTimeToUTCString, + buildUTMTagData, } from 'modules/discoveryBidAdapter.js'; +import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from '../../../libraries/fpdUtils/pageInfo.js'; import * as utils from 'src/utils.js'; +import {getHLen} from '../../../libraries/navigatorData/navigatorData.js'; describe('discovery:BidAdapterTests', function () { let sandbox; @@ -21,6 +19,7 @@ describe('discovery:BidAdapterTests', function () { sandbox = sinon.sandbox.create(); sandbox.stub(storage, 'getCookie'); sandbox.stub(storage, 'setCookie'); + sandbox.stub(storage, 'getDataFromLocalStorage'); sandbox.stub(utils, 'generateUUID').returns('new-uuid'); sandbox.stub(utils, 'parseUrl').returns({ search: { @@ -257,6 +256,11 @@ describe('discovery:BidAdapterTests', function () { getPmgUID(); expect(storage.setCookie.calledOnce).to.be.false; }); + it('should return other ID from storage and cookie', () => { + spec.buildRequests(bidRequestData.bids, bidRequestData); + expect(storage.getCookie.called).to.be.true; + expect(storage.getDataFromLocalStorage.called).to.be.true; + }); }) describe('buildUTMTagData function', function() { it('should set UTM cookie', () => { @@ -590,7 +594,7 @@ describe('discovery Bid Adapter Tests', function () { const response = event.data; if (!response.optout && response.mguid) { - storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCurrentTimeToUTCString()); + storage.setCookie(COOKIE_KEY_MGUID, response.mguid, getCookieTimeToUTCString()); } } @@ -633,5 +637,28 @@ describe('discovery Bid Adapter Tests', function () { expect(storage.setCookie.notCalled).to.be.true; }); }); + describe('getHLen', () => { + it('should return the correct length of history when accessible', () => { + const mockWindow = { + top: { + history: { + length: 3 + } + } + }; + const result = getHLen(mockWindow); + expect(result).to.equal(3); + }); + + it('should return undefined when accessing win.top.history.length throws an error', () => { + const mockWindow = { + get top() { + throw new Error('Access denied'); + } + }; + const result = getHLen(mockWindow); + expect(result).be.undefined; + }); + }); }); }); diff --git a/test/spec/modules/djaxBidAdapter_spec.js b/test/spec/modules/djaxBidAdapter_spec.js new file mode 100644 index 00000000000..afa9a36eab7 --- /dev/null +++ b/test/spec/modules/djaxBidAdapter_spec.js @@ -0,0 +1,100 @@ +import { expect } from 'chai'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { spec } from '../../../modules/djaxBidAdapter.js'; + +const DOMAIN = 'https://revphpe.djaxbidder.com/header_bidding_vast/'; +const ENDPOINT = DOMAIN + 'www/admin/plugins/Prebid/getAd.php'; + +describe('Djax Adapter', function() { + const adapter = newBidder(spec); + + describe('inherited functions', function () { + it('should exists and is a function', function () { + expect(adapter.callBids).to.exist.and.to.be.a('function'); + }); + }); + + describe('isBidRequestValidForBanner', () => { + let bid = { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333', + }; + + it('should return true when required params found', () => { + expect(spec.isBidRequestValid(bid)).to.equal(true); + }); + }); + + describe('buildRequestsForBanner', () => { + let bidRequests = [ + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333' + } + ]; + + it('sends bid request to ENDPOINT via POST', () => { + const request = spec.buildRequests(bidRequests); + expect(request.url).to.contain(ENDPOINT); + expect(request.method).to.equal('POST'); + }); + }); + + describe('interpretResponseForBanner', () => { + let bidRequests = [ + { + 'bidder': 'djax', + 'params': { + 'publisherId': 2 + }, + 'adUnitCode': 'adunit-code', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [300, 250] + ] + } + }, + 'sizes': [[300, 250]], + 'bidId': '26e88c3c703e66', + 'bidderRequestId': '1a8ff729f6c1a3', + 'auctionId': 'cb65d954-ffe1-4f4a-8603-02b521c00333' + } + ]; + + it('handles nobid responses', () => { + var request = spec.buildRequests(bidRequests); + let response = ''; + let result = spec.interpretResponse(response, request[0]); + expect(result.length).to.equal(0); + }); + }); +}); diff --git a/test/spec/modules/docereeAdManagerBidAdapter_spec.js b/test/spec/modules/docereeAdManagerBidAdapter_spec.js index 26b054f4e29..704b9c48d3a 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -1,30 +1,32 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/docereeAdManagerBidAdapter.js'; +import { spec, getPayload, getPageUrl } from '../../../modules/docereeAdManagerBidAdapter.js'; import { config } from '../../../src/config.js'; +import * as utils from '../../../src/utils.js'; describe('docereeadmanager', function () { config.setConfig({ docereeadmanager: { user: { data: { + userId: '', email: '', firstname: '', lastname: '', - mobile: '', specialization: '', - organization: '', hcpid: '', - dob: '', gender: '', city: '', state: '', - country: '', + zipcode: '', + hashedNPI: '', hashedhcpid: '', hashedemail: '', hashedmobile: '', - userid: '', - zipcode: '', - userconsent: '', + country: '', + organization: '', + dob: '', + platformUid: '', + mobile: '', }, }, }, @@ -34,6 +36,7 @@ describe('docereeadmanager', function () { bidder: 'docereeadmanager', params: { placementId: 'DOC-19-1', + publisherUrl: 'xxxxxx.com/xxxx', gdpr: '1', gdprconsent: 'CPQfU1jPQfU1jG0AAAENAwCAAAAAAAAAAAAAAAAAAAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g', @@ -122,4 +125,98 @@ describe('docereeadmanager', function () { .empty; }); }); + + describe('getPageUrl', function () { + it('should return an url string', function () { + const result = getPageUrl(); + expect(result).to.equal(utils.getWindowSelf().location.href); + }); + }); + + describe('payload', function () { + it('should return payload with the correct data', function () { + const data = { + userId: 'xxxxx', + email: 'xxxx@mail.com', + firstname: 'Xxxxx', + lastname: 'Xxxxxx', + specialization: 'Xxxxxxxxx', + hcpid: 'xxxxxxx', + gender: 'Xxxx', + city: 'Xxxxx', + state: 'Xxxxxx', + zipcode: 'XXXXXX', + hashedNPI: 'xxxxxx', + hashedhcpid: 'xxxxxxx', + hashedemail: 'xxxxxxx', + hashedmobile: 'xxxxxxx', + country: 'Xxxxxx', + organization: 'Xxxxxx', + dob: 'xx-xx-xxxx', + platformUid: 'Xx.xxx.xxxxxx', + mobile: 'XXXXXXXXXX', + } + bid = { ...bid, params: { ...bid.params, placementId: 'DOC-19-1' } } + const buildRequests = { + gdprConsent: { + consentString: 'COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprApplies: false + } + } + const payload = getPayload(bid, data, buildRequests); + + const payloadData = payload.data; + expect(payloadData).to.have.all.keys( + 'userid', + 'email', + 'firstname', + 'lastname', + 'specialization', + 'hcpid', + 'gender', + 'city', + 'state', + 'zipcode', + 'hashedNPI', + 'pb', + 'adunit', + 'requestId', + 'hashedhcpid', + 'hashedemail', + 'hashedmobile', + 'country', + 'organization', + 'dob', + 'userconsent', + 'mobile', + 'pageurl', + 'consent' + ); + expect(payloadData.userid).to.equal('Xx.xxx.xxxxxx'); + expect(payloadData.email).to.equal('xxxx@mail.com'); + expect(payloadData.firstname).to.equal('Xxxxx'); + expect(payloadData.lastname).to.equal('Xxxxxx'); + expect(payloadData.specialization).to.equal('Xxxxxxxxx'); + expect(payloadData.hcpid).to.equal('xxxxxxx'); + expect(payloadData.gender).to.equal('Xxxx'); + expect(payloadData.city).to.equal('Xxxxx'); + expect(payloadData.state).to.equal('Xxxxxx'); + expect(payloadData.zipcode).to.equal('XXXXXX'); + expect(payloadData.hashedNPI).to.equal('xxxxxx'); + expect(payloadData.pb).to.equal(1); + expect(payloadData.userconsent).to.equal(1); + expect(payloadData.dob).to.equal('xx-xx-xxxx'); + expect(payloadData.organization).to.equal('Xxxxxx'); + expect(payloadData.country).to.equal('Xxxxxx'); + expect(payloadData.hashedmobile).to.equal('xxxxxxx'); + expect(payloadData.hashedemail).to.equal('xxxxxxx'); + expect(payloadData.hashedhcpid).to.equal('xxxxxxx'); + expect(payloadData.requestId).to.equal('testing'); + expect(payloadData.mobile).to.equal('XXXXXXXXXX'); + expect(payloadData.adunit).to.equal('DOC-19-1'); + expect(payloadData.pageurl).to.equal('xxxxxx.com/xxxx'); + expect(payloadData.consent.gdprstr).to.equal('COwK6gaOwK6gaFmAAAENAPCAAAAAAAAAAAAAAAAAAAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw'); + expect(payloadData.consent.gdpr).to.equal(0); + }) + }) }); diff --git a/test/spec/modules/driftpixelBidAdapter_spec.js b/test/spec/modules/driftpixelBidAdapter_spec.js new file mode 100644 index 00000000000..da84235b404 --- /dev/null +++ b/test/spec/modules/driftpixelBidAdapter_spec.js @@ -0,0 +1,458 @@ +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/driftpixelBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; + +const ENDPOINT = 'https://pbjs.driftpixel.live'; + +const defaultRequest = { + adUnitCode: 'test', + bidId: '1', + requestId: 'qwerty', + ortb2: { + source: { + tid: 'auctionId' + } + }, + ortb2Imp: { + ext: { + tid: 'tr1', + } + }, + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 200] + ] + } + }, + bidder: 'driftpixel', + params: { + env: 'driftpixel', + pid: '40', + ext: {} + }, + bidRequestsCount: 1 +}; + +const defaultRequestVideo = deepClone(defaultRequest); +defaultRequestVideo.mediaTypes = { + video: { + playerSize: [640, 480], + context: 'instream', + skipppable: true + } +}; + +const videoBidderRequest = { + bidderCode: 'driftpixel', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'driftpixel', + bids: [{bidId: 'qwerty'}] +}; + +describe('driftpixelBidAdapter', () => { + describe('isBidRequestValid', function () { + it('should return false when request params is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required env param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.env; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when required pid param is missing', function () { + const invalidRequest = deepClone(defaultRequest); + delete invalidRequest.params.pid; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return false when video.playerSize is missing', function () { + const invalidRequest = deepClone(defaultRequestVideo); + delete invalidRequest.mediaTypes.video.playerSize; + expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); + }); + + it('should return true when required params found', function () { + expect(spec.isBidRequestValid(defaultRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + beforeEach(function () { + config.resetConfig(); + }); + + it('should send request with correct structure', function () { + const request = spec.buildRequests([defaultRequest], {}); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal(ENDPOINT + '/bid'); + expect(request.options).to.have.property('contentType').and.to.equal('application/json'); + expect(request).to.have.property('data'); + }); + + it('should build basic request structure', function () { + const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; + expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); + expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); + expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); + expect(request).to.have.property('bc').and.to.equal(1); + expect(request).to.have.property('floor').and.to.equal(null); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); + expect(request).to.have.property('gdprApplies').and.to.equal(0); + expect(request).to.have.property('consentString').and.to.equal(''); + expect(request).to.have.property('userEids').and.to.deep.equal([]); + expect(request).to.have.property('usPrivacy').and.to.equal(''); + expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); + expect(request).to.have.property('ext').and.to.deep.equal({}); + expect(request).to.have.property('env').and.to.deep.equal({ + env: 'driftpixel', + pid: '40' + }); + expect(request).to.have.property('device').and.to.deep.equal({ + ua: navigator.userAgent, + lang: navigator.language + }); + }); + + it('should build request with schain', function () { + const schainRequest = deepClone(defaultRequest); + schainRequest.schain = { + validation: 'strict', + config: { + ver: '1.0' + } + }; + const request = JSON.parse(spec.buildRequests([schainRequest], {}).data)[0]; + expect(request).to.have.property('schain').and.to.deep.equal({ + validation: 'strict', + config: { + ver: '1.0' + } + }); + }); + + it('should build request with location', function () { + const bidderRequest = { + refererInfo: { + page: 'page', + location: 'location', + domain: 'domain', + ref: 'ref', + isAmp: false + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('location'); + const location = request.location; + expect(location).to.have.property('page').and.to.equal('page'); + expect(location).to.have.property('location').and.to.equal('location'); + expect(location).to.have.property('domain').and.to.equal('domain'); + expect(location).to.have.property('ref').and.to.equal('ref'); + expect(location).to.have.property('isAmp').and.to.equal(false); + }); + + it('should build request with ortb2 info', function () { + const ortb2Request = deepClone(defaultRequest); + ortb2Request.ortb2 = { + site: { + name: 'name' + } + }; + const request = JSON.parse(spec.buildRequests([ortb2Request], {}).data)[0]; + expect(request).to.have.property('ortb2').and.to.deep.equal({ + site: { + name: 'name' + } + }); + }); + + it('should build request with ortb2Imp info', function () { + const ortb2ImpRequest = deepClone(defaultRequest); + ortb2ImpRequest.ortb2Imp = { + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }; + const request = JSON.parse(spec.buildRequests([ortb2ImpRequest], {}).data)[0]; + expect(request).to.have.property('ortb2Imp').and.to.deep.equal({ + ext: { + data: { + pbadslot: 'home1', + adUnitSpecificAttribute: '1' + } + } + }); + }); + + it('should build request with valid bidfloor', function () { + const bfRequest = deepClone(defaultRequest); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); + const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; + expect(request).to.have.property('floor').and.to.equal(5); + }); + + it('should build request with gdpr consent data if applies', function () { + const bidderRequest = { + gdprConsent: { + gdprApplies: true, + consentString: 'qwerty' + } + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('gdprApplies').and.equals(1); + expect(request).to.have.property('consentString').and.equals('qwerty'); + }); + + it('should build request with usp consent data if applies', function () { + const bidderRequest = { + uspConsent: '1YA-' + }; + const request = JSON.parse(spec.buildRequests([defaultRequest], bidderRequest).data)[0]; + expect(request).to.have.property('usPrivacy').and.equals('1YA-'); + }); + + it('should build request with extended ids', function () { + const idRequest = deepClone(defaultRequest); + idRequest.userIdAsEids = [ + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} + ]; + const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; + expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); + }); + + it('should build request with video', function () { + const request = JSON.parse(spec.buildRequests([defaultRequestVideo], {}).data)[0]; + expect(request).to.have.property('video').and.to.deep.equal({ + playerSize: [640, 480], + context: 'instream', + skipppable: true + }); + expect(request).to.have.property('sizes').and.to.deep.equal(['640x480']); + }); + }); + + describe('interpretResponse', function () { + it('should return empty bids', function () { + const serverResponse = { + body: { + data: null + } + }; + + const invalidResponse = spec.interpretResponse(serverResponse, {}); + expect(invalidResponse).to.be.an('array').that.is.empty; + }); + + it('should interpret valid response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + meta: { + advertiserDomains: ['driftpixel'] + }, + ext: { + pixels: [ + ['iframe', 'surl1'], + ['image', 'surl2'], + ] + } + }] + } + }; + + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponse[0]; + expect(validResponse).to.be.an('array').that.is.not.empty; + expect(bid.requestId).to.equal('qwerty'); + expect(bid.cpm).to.equal(1); + expect(bid.currency).to.equal('USD'); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ttl).to.equal(600); + expect(bid.meta).to.deep.equal({advertiserDomains: ['driftpixel']}); + }); + + it('should interpret valid banner response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 300, + height: 250, + ttl: 600, + mediaType: 'banner', + creativeId: 'xe-demo-banner', + ad: 'ad', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('banner'); + expect(bid.creativeId).to.equal('xe-demo-banner'); + expect(bid.ad).to.equal('ad'); + }); + + it('should interpret valid video response', function () { + const serverResponse = { + body: { + data: [{ + requestId: 'qwerty', + cpm: 1, + currency: 'USD', + width: 600, + height: 480, + ttl: 600, + mediaType: 'video', + creativeId: 'xe-demo-video', + ad: 'vast-xml', + meta: {} + }] + } + }; + + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); + const bid = validResponseBanner[0]; + expect(validResponseBanner).to.be.an('array').that.is.not.empty; + expect(bid.mediaType).to.equal('video'); + expect(bid.creativeId).to.equal('xe-demo-video'); + expect(bid.ad).to.equal('vast-xml'); + }); + }); + + describe('getUserSyncs', function () { + it('shoukd handle no params', function () { + const opts = spec.getUserSyncs({}, []); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should return empty if sync is not allowed', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: false}); + expect(opts).to.be.an('array').that.is.empty; + }); + + it('should allow iframe sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: true, pixelEnabled: false}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('iframe'); + expect(opts[0].url).to.equal('surl1?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }]); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=0&gdpr_consent='); + }); + + it('should allow pixel sync and parse consent params', function () { + const opts = spec.getUserSyncs({iframeEnabled: false, pixelEnabled: true}, [{ + body: { + data: [{ + requestId: 'qwerty', + ext: { + pixels: [ + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], + ] + } + }] + } + }], { + gdprApplies: 1, + consentString: '1YA-' + }); + expect(opts.length).to.equal(1); + expect(opts[0].type).to.equal('image'); + expect(opts[0].url).to.equal('surl2?a=b&us_privacy=&gdpr=1&gdpr_consent=1YA-'); + }); + }); + + describe('getBidFloor', function () { + 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: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return null when currency is not USD', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'EUR'}) + }; + const result = getBidFloor(bid); + expect(result).to.be.null; + }); + + it('should return floor value when everything is correct', () => { + const bid = { + getFloor: () => ({floor: 5, currency: 'USD'}) + }; + const result = getBidFloor(bid); + expect(result).to.equal(5); + }); + }); +}) diff --git a/test/spec/modules/dsaControl_spec.js b/test/spec/modules/dsaControl_spec.js index 45392d58c04..1744d1d5bab 100644 --- a/test/spec/modules/dsaControl_spec.js +++ b/test/spec/modules/dsaControl_spec.js @@ -108,6 +108,5 @@ describe('DSA transparency', () => { }) }) }) - it('should accept bids regardless of dsa when "required" any other value') }); }); diff --git a/test/spec/modules/dsp_genieeBidAdapter_spec.js b/test/spec/modules/dsp_genieeBidAdapter_spec.js index 94ec1011fbf..576fd9e404b 100644 --- a/test/spec/modules/dsp_genieeBidAdapter_spec.js +++ b/test/spec/modules/dsp_genieeBidAdapter_spec.js @@ -38,7 +38,8 @@ describe('Geniee adapter tests', () => { ext: { test: 1 }, - id: 'bid-id' + id: 'bid-id', + secure: 1 } ], test: 1 @@ -121,7 +122,9 @@ describe('Geniee adapter tests', () => { currency: 'JPY', mediaType: 'banner', meta: { - advertiserDomains: ['geniee.co.jp'] + advertiserDomains: ['geniee.co.jp'], + primaryCatId: 'IAB1', + secondaryCatIds: [] }, netRevenue: true, requestId: 'bid-id', diff --git a/test/spec/modules/dspxBidAdapter_spec.js b/test/spec/modules/dspxBidAdapter_spec.js index 841fc087613..ad7bc827837 100644 --- a/test/spec/modules/dspxBidAdapter_spec.js +++ b/test/spec/modules/dspxBidAdapter_spec.js @@ -1,7 +1,9 @@ import { expect } from 'chai'; +import { config } from 'src/config.js'; import { spec } from 'modules/dspxBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { deepClone } from '../../../src/utils'; +import {BANNER} from '../../../src/mediaTypes'; const ENDPOINT_URL = 'https://buyer.dspx.tv/request/'; const ENDPOINT_URL_DEV = 'https://dcbuyer.dspx.tv/request/'; @@ -33,12 +35,19 @@ describe('dspxAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { - 'someIncorrectParam': 0 - }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + const invalidBid = { + bidId: '30b31c1838de1e', + bidder: 'dspx', + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + someIncorrectParam: 0 + } + } + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -77,7 +86,26 @@ describe('dspxAdapter', function () { 'sharedid': { 'id': '01EXPPGZ9C8NKG1MTXVHV98505', 'third': '01EXPPGZ9C8NKG1MTXVHV98505' - } + }, + 'pubProvidedId': [{ + 'source': 'puburl2.com', + 'uids': [{ + 'id': 'pubid2' + }, { + 'id': 'pubid2-123' + }] + }, { + 'source': 'puburl.com', + 'uids': [{ + 'id': 'pubid1', + 'atype': 1, + 'ext': { + 'stype': 'ppuid' + } + }] + }], + 'euid': {}, + 'tdid': 'tdid_ID', }, 'crumbs': { 'pubcid': 'e09ab6a3-ae74-4f01-b2e8-81b141d6dc61' @@ -228,12 +256,54 @@ describe('dspxAdapter', function () { } }; + // With ortb2 + var bidderRequestWithORTB = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + }, + ortb2: { + source: {}, + site: { + domain: 'buyer', + publisher: { + domain: 'buyer' + }, + page: 'http://buyer/schain.php?ver=8.5.0-pre:latest-dev-build&pbjs_debug=true', + ref: 'http://buyer/pbjsv/', + content: { + id: 'contentID', + episode: 1, + title: 'contentTitle', + series: 'contentSeries', + season: 'contentSeason 3', + artist: 'contentArtist', + genre: 'rock', + isrc: 'contentIsrc', + url: 'https://content-url.com/', + context: 1, + keywords: 'kw1,kw2,keqword 3', + livestream: 0, + cat: [ + 'IAB1-1', + 'IAB1-2', + 'IAB2-10' + ] + } + }, + } + }; + var request1 = spec.buildRequests([bidRequests[0]], bidderRequest)[0]; it('sends bid request to our endpoint via GET', 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').replace(/pbver=.*?&/g, 'pbver=test&'); - expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&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_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=6682&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e1&pbver=test&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&auctionId=1d1a030790a475&pbcode=testDiv1&media_types%5Bbanner%5D=300x250&schain%5Bver%5D=1.0&schain%5Bcomplete%5D=1&schain%5Bnodes%5D%5B0%5D%5Basi%5D=example.com&schain%5Bnodes%5D%5B0%5D%5Bsid%5D=0&schain%5Bnodes%5D%5B0%5D%5Bhp%5D=1&schain%5Bnodes%5D%5B0%5D%5Brid%5D=bidrequestid&schain%5Bnodes%5D%5B0%5D%5Bdomain%5D=example.com&did_netid=123&did_id5=ID5-ZHMOcvSShIBZiIth_yYh9odjNFxVEmMQ_i5TArPfWw!ID5*dtrjfV5mPLasyya5TW2IE9oVzQZwx7xRPGyAYS4hcWkAAOoxoFef4bIoREpQys8x&did_id5_linktype=2&did_uid2=456&did_sharedid=01EXPPGZ9C8NKG1MTXVHV98505&did_pubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61&did_tdid=tdid_ID&did_ppuid=1%3Apuburl.com%3Apubid1&did_cpubcid=e09ab6a3-ae74-4f01-b2e8-81b141d6dc61'); }); var request2 = spec.buildRequests([bidRequests[1]], bidderRequest)[0]; @@ -281,6 +351,14 @@ describe('dspxAdapter', function () { expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); }); + var request7 = spec.buildRequests([bidRequests[5]], bidderRequestWithORTB)[0]; + it('ortb2 iab_content test', function () { + expect(request7.method).to.equal('GET'); + expect(request7.url).to.equal('http://localhost'); + let data = request7.data.replace(/rnd=\d+\&/g, '').replace(/ref=.*\&bid/g, 'bid').replace(/pbver=.*?&/g, 'pbver=test&'); + expect(data).to.equal('_f=auto&alternative=prebid_js&inventory_item_id=107&srw=300&srh=250&idt=100&bid_id=30b31c1838de1e4&pbver=test&pfilter%5Btest%5D=1&pfilter%5Bgdpr_consent%5D=BOJ%2FP2HOJ%2FP2HABABMAAAAAZ%2BA%3D%3D&pfilter%5Bgdpr%5D=true&pfilter%5Biab_content%5D=cat%3AIAB1-1%7CIAB1-2%7CIAB2-10%2Cepisode%3A1%2Ccontext%3A1%2Cid%3AcontentID%2Ctitle%3AcontentTitle%2Cseries%3AcontentSeries%2Cseason%3AcontentSeason%25203%2Cartist%3AcontentArtist%2Cgenre%3Arock%2Cisrc%3AcontentIsrc%2Curl%3Ahttps%253A%252F%252Fcontent-url.com%252F%2Ckeywords%3Akw1%252Ckw2%252Ckeqword%25203&prebidDevMode=1&auctionId=1d1a030790a478&pbcode=testDiv3&media_types%5Bvideo%5D=640x480&media_types%5Bbanner%5D=300x250&vctx=instream&vpl%5Bmimes%5D%5B0%5D=video%2Fmp4&vpl%5Bprotocols%5D%5B0%5D=1&vpl%5Bprotocols%5D%5B1%5D=2&vpl%5Bplaybackmethod%5D%5B0%5D=2&vpl%5Bskip%5D=1'); + }); + // bidfloor tests const getFloorResponse = {currency: 'EUR', floor: 5}; let testBidRequest = deepClone(bidRequests[1]); @@ -321,6 +399,109 @@ describe('dspxAdapter', function () { }); }); + describe('google topics handling', () => { + afterEach(() => { + config.resetConfig(); + }); + + const REQPARAMS = { + refererInfo: { + referer: 'some_referrer.net' + }, + gdprConsent: { + consentString: 'BOJ/P2HOJ/P2HABABMAAAAAZ+A==', + vendorData: {someData: 'value'}, + gdprApplies: true + } + }; + + const defaultRequest = { + 'bidder': 'dspx', + 'params': { + 'placement': '6682', + 'pfilter': { + 'floorprice': 1000000, + 'private_auction': 0, + 'geo': { + 'country': 'DE' + } + }, + 'bcat': 'IAB2,IAB4', + 'dvt': 'desktop' + }, + 'sizes': [ + [300, 250] + ], + 'bidId': '30b31c1838de1e1', + 'bidderRequestId': '22edbae2733bf61', + 'auctionId': '1d1a030790a475', + 'adUnitCode': 'testDiv1', + }; + + it('does pass segtax, segclass, segments for google topics data', () => { + const GOOGLE_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + {id: '717'}, {id: '808'}, + ] + } + ] + }, + }, + } + config.setConfig(GOOGLE_TOPICS_DATA); + const request = spec.buildRequests([defaultRequest], { ...REQPARAMS, ...GOOGLE_TOPICS_DATA })[0]; + expect(request.data).to.contain('segtx=600&segcl=v1&segs=717%2C808'); + }); + + it('does not pass topics params for invalid topics data', () => { + const INVALID_TOPICS_DATA = { + ortb2: { + user: { + data: [ + { + segment: [] + }, + { + segment: [{id: ''}] + }, + { + segment: [{id: null}] + }, + { + segment: [{id: 'dummy'}, {id: '123'}] + }, + { + ext: { + segtax: 600, + segclass: 'v1', + }, + segment: [ + { + name: 'dummy' + } + ] + }, + ] + } + } + }; + + config.setConfig(INVALID_TOPICS_DATA); + let request = spec.buildRequests([defaultRequest], { ...REQPARAMS, ...INVALID_TOPICS_DATA })[0]; + expect(request.data).to.not.contain('segtax'); + expect(request.data).to.not.contain('segclass'); + expect(request.data).to.not.contain('segments'); + }); + }); + describe('interpretResponse', function () { let serverResponse = { 'body': { @@ -427,7 +608,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.include.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]); }); @@ -447,7 +628,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[1])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); @@ -466,7 +647,7 @@ describe('dspxAdapter', function () { } }]; let result = spec.interpretResponse(serverVideoResponseVastUrl, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[2])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[2])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); diff --git a/test/spec/modules/e_volutionBidAdapter_spec.js b/test/spec/modules/e_volutionBidAdapter_spec.js index d488048060a..4777b73aa3c 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -1,117 +1,302 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/e_volutionBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/e_volutionBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'e_volution'; describe('EvolutionTechBidAdapter', function () { - let bids = [{ - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 - }, - mediaTypes: { - banner: { - sizes: [[300, 250]], + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', - params: { - placementId: 0 + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { - video: { - playerSize: [300, 250] + [BANNER]: { + sizes: [[300, 250]] } }, - userId: { - id5id: 'id5id' - } - }, { - bidId: '23fhj33i987f', - bidder: 'e_volution', params: { - placementId: 0 - }, - mediaTypes: { - native: {} - }, - userId: { - id5id: 'id5id' + } - }]; + } const bidderRequest = { - uspConsent: 'uspConsent', - gdprConsent: 'gdprConsent' + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; 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(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bids[0].params.placementId; - expect(spec.isBidRequestValid(bids[0])).to.be.false; + 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://service.e-volution.ai/?c=o&m=multi'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'ccpa', 'gdpr'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + '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.ccpa).to.be.equal('uspConsent'); - expect(data.gdpr).to.be.equal('gdprConsent'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); + }); - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'bidfloor', 'eids'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('banner'); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - placement = data['placements'][1]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'wPlayer', 'hPlayer', - 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', - 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('video'); + 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; + } + } + }); - placement = data['placements'][2]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'bidfloor', 'eids', 'native'); - expect(placement.placementId).to.equal(0); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal('native'); + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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.placements).to.be.an('array').that.is.empty; + 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; }); }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -128,7 +313,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -137,15 +323,16 @@ describe('EvolutionTechBidAdapter', function () { 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('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.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -160,7 +347,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -177,6 +365,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 = { @@ -195,7 +384,8 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -216,6 +406,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 = { @@ -282,18 +473,4 @@ 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'); - } - }); - }); }); diff --git a/test/spec/modules/ebdrBidAdapter_spec.js b/test/spec/modules/ebdrBidAdapter_spec.js deleted file mode 100644 index 1c46381500f..00000000000 --- a/test/spec/modules/ebdrBidAdapter_spec.js +++ /dev/null @@ -1,245 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/ebdrBidAdapter.js'; -import { VIDEO, BANNER } from 'src/mediaTypes.js'; -import * as utils from 'src/utils.js'; - -describe('ebdrBidAdapter', function () { - let bidRequests; - - beforeEach(function () { - bidRequests = [ - { - code: 'div-gpt-ad-1460505748561-0', - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 600]], - } - }, - bidder: 'ebdr', - params: { - zoneid: '99999', - bidfloor: '1.00', - IDFA: 'xxx-xxx', - ADID: 'xxx-xxx', - latitude: '34.089811', - longitude: '-118.392805' - }, - bidId: '2c5e8a1a84522d', - bidderRequestId: '1d0c4017f02458', - auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' - }, { - adUnitCode: 'div-gpt-ad-1460505748561-1', - mediaTypes: { - video: { - context: 'instream', - playerSize: [300, 250] - } - }, - bidder: 'ebdr', - params: { - zoneid: '99998', - bidfloor: '1.00', - IDFA: 'xxx-xxx', - ADID: 'xxx-xxx', - latitude: '34.089811', - longitude: '-118.392805' - }, - bidId: '23a01e95856577', - bidderRequestId: '1d0c4017f02458', - auctionId: '9adc85ed-43ee-4a78-816b-52b7e578f314' - } - ]; - }); - - describe('spec.isBidRequestValid', function () { - it('should return true when the required params are passed', function () { - const bidRequest = bidRequests[0]; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return true when the only required param is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = { - zoneid: '99998', - bidfloor: '1.00', - }; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return true when the "bidfloor" param is missing', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = { - zoneid: '99998', - }; - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); - }); - - it('should return false when no bid params are passed', function () { - const bidRequest = bidRequests[0]; - bidRequest.params = {}; - expect(spec.isBidRequestValid(bidRequest)).to.equal(false); - }); - - it('should return false when a bid request is not passed', function () { - expect(spec.isBidRequestValid()).to.equal(false); - expect(spec.isBidRequestValid({})).to.equal(false); - }); - }); - - describe('spec.buildRequests', function () { - describe('for banner bids', function () { - it('must handle an empty bid size', function () { - bidRequests[0].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); - const bidRequest = {}; - bidRequest['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: null, h: null }; - expect(requests.bids['2c5e8a1a84522d']).to.deep.equals(bidRequest['2c5e8a1a84522d']); - }); - it('should create a single GET', function () { - bidRequests[0].mediaTypes = { banner: {} }; - bidRequests[1].mediaTypes = { banner: {} }; - const requests = spec.buildRequests(bidRequests); - expect(requests.method).to.equal('GET'); - }); - it('must parse bid size from a nested array', function () { - const width = 640; - const height = 480; - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {sizes: [[ width, height ]]} }; - const requests = spec.buildRequests([ bidRequest ]); - const data = {}; - data['2c5e8a1a84522d'] = { mediaTypes: BANNER, w: width, h: height }; - expect(requests.bids['2c5e8a1a84522d']).to.deep.equal(data['2c5e8a1a84522d']); - }); - }); - describe('for video bids', function () { - it('must handle an empty bid size', function () { - bidRequests[1].mediaTypes = { video: {} }; - const requests = spec.buildRequests(bidRequests); - const bidRequest = {}; - bidRequest['23a01e95856577'] = { mediaTypes: VIDEO, w: null, h: null }; - expect(requests.bids['23a01e95856577']).to.deep.equals(bidRequest['23a01e95856577']); - }); - - it('should create a GET request for each bid', function () { - const bidRequest = bidRequests[1]; - const requests = spec.buildRequests([ bidRequest ]); - expect(requests.method).to.equal('GET'); - }); - }); - }); - - describe('spec.interpretResponse', function () { - describe('for video bids', function () { - it('should return no bids if the response is not valid', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { video: {} }; - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return a valid video bid response', function () { - const ebdrReq = {bids: {}}; - bidRequests.forEach(bid => { - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, - w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], - h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] - }; - }); - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '23a01e95856577', impid: '23a01e95856577', price: 0.81, adid: 'abcde-12345', nurl: 'https://cdn0.bnmla.com/vtest.xml', adm: '\nStatic VASTStatic VAST Tag00:00:15https//www.engagebdr.com/c', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD'}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); - expect(bidResponse[0]).to.deep.equal({ - requestId: bidRequests[1].bidId, - vastXml: serverResponse.seatbid[0].bid[0].adm, - mediaType: 'video', - creativeId: serverResponse.seatbid[0].bid[0].crid, - cpm: serverResponse.seatbid[0].bid[0].price, - width: serverResponse.seatbid[0].bid[0].w, - height: serverResponse.seatbid[0].bid[0].h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - vastUrl: serverResponse.seatbid[0].bid[0].nurl, - meta: { - advertiserDomains: [ - 'advertiserdomain.com' - ] - } - }); - }); - }); - - describe('for banner bids', function () { - it('should return no bids if the response is not valid', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: null }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return no bids if the response is empty', function () { - const bidRequest = bidRequests[0]; - bidRequest.mediaTypes = { banner: {} }; - const bidResponse = spec.interpretResponse({ body: [] }, { bidRequest }); - expect(bidResponse.length).to.equal(0); - }); - - it('should return valid banner bid responses', function () { - const ebdrReq = {bids: {}}; - bidRequests.forEach(bid => { - let _mediaTypes = (bid.mediaTypes && bid.mediaTypes.video ? VIDEO : BANNER); - ebdrReq.bids[bid.bidId] = {mediaTypes: _mediaTypes, - w: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][0] : bid.mediaTypes[_mediaTypes].playerSize[0], - h: _mediaTypes == BANNER ? bid.mediaTypes[_mediaTypes].sizes[0][1] : bid.mediaTypes[_mediaTypes].playerSize[1] - }; - }); - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '

', adomain: ['advertiserdomain.com'], iurl: '', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; - const bidResponse = spec.interpretResponse({ body: serverResponse }, ebdrReq); - expect(bidResponse[0]).to.deep.equal({ - requestId: bidRequests[ 0 ].bidId, - ad: serverResponse.seatbid[0].bid[0].adm, - mediaType: 'banner', - creativeId: serverResponse.seatbid[0].bid[0].crid, - cpm: serverResponse.seatbid[0].bid[0].price, - width: serverResponse.seatbid[0].bid[0].w, - height: serverResponse.seatbid[0].bid[0].h, - currency: 'USD', - netRevenue: true, - ttl: 3600, - meta: { - advertiserDomains: [ - 'advertiserdomain.com' - ] - }, - }); - }); - }); - }); - describe('spec.getUserSyncs', function () { - let syncOptions - beforeEach(function () { - syncOptions = { - enabledBidders: ['ebdr'], // only these bidders are allowed to sync - pixelEnabled: true - } - }); - it('sucess with usersync url', function () { - const serverResponse = {id: '1d0c4017f02458', seatbid: [{bid: [{id: '2c5e8a1a84522d', impid: '2c5e8a1a84522d', price: 0.81, adid: 'abcde-12345', nurl: '', adm: '
', adomain: ['advertiserdomain.com'], iurl: 'https://match.bnmla.com/usersync?sspid=59&redir=', cid: 'campaign1', crid: 'abcde-12345', w: 300, h: 250}], seat: '19513bcfca8006'}], bidid: '19513bcfca8006', cur: 'USD', w: 300, h: 250}; - const result = []; - result.push({type: 'image', url: 'https://match.bnmla.com/usersync?sspid=59&redir='}); - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - - it('sucess without usersync url', function () { - const serverResponse = {id: '1d0c4017f02458', 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 result = []; - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - it('empty response', function () { - const serverResponse = {}; - const result = []; - expect(spec.getUserSyncs(syncOptions, { body: serverResponse })).to.deep.equal(result); - }); - }); -}); diff --git a/test/spec/modules/eclickadsBidAdapter_spec.js b/test/spec/modules/eclickadsBidAdapter_spec.js new file mode 100644 index 00000000000..aadb8f41a64 --- /dev/null +++ b/test/spec/modules/eclickadsBidAdapter_spec.js @@ -0,0 +1,214 @@ +import { expect } from 'chai'; +import { + spec, + ENDPOINT, + BIDDER_CODE, +} from '../../../modules/eclickadsBidAdapter.js'; +import { NATIVE, BANNER, VIDEO } from '../../../src/mediaTypes.js'; +import { deepClone } from '../../../src/utils.js'; +import { config } from '../../../src/config.js'; + +describe('eclickadsBidAdapter', () => { + const bidItem = { + bidder: BIDDER_CODE, + params: { + zid: '7096', + }, + }; + const eclickadsBidderConfigData = { + orig_aid: 'xqf7zdmg7the65ac.1718271138.des', + fosp_aid: '1013000403', + fosp_uid: '7aab24a4663258a2c1d76a08b20f7e6e', + id: '84b2a41c4299bb9b8924423e', + myvne_id: '1013000403', + }; + const bidRequest = { + code: 'test-div', + size: [[320, 85]], + mediaTypes: { + [NATIVE]: { + title: { + required: true, + }, + body: { + required: true, + }, + image: { + required: true, + }, + sponsoredBy: { + required: true, + }, + icon: { + required: false, + }, + }, + }, + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + site: { + name: 'example', + domain: 'page.example.com', + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + ext: { + data: eclickadsBidderConfigData, + }, + }, + }, + }; + + describe('isBidRequestValid', () => { + it('should return false when atleast one of required params is missing', () => { + const bid = deepClone(bidItem); + delete bid.params.zid; + expect(spec.isBidRequestValid(bid)).to.be.false; + }); + it('should return true if there is correct required params and mediatype', () => { + bidItem.params.mediaTypes == NATIVE; + expect(spec.isBidRequestValid(bidItem)).to.be.true; + }); + it('should return true if there is no size', () => { + const bid = deepClone(bidItem); + delete bid.params.size; + expect(spec.isBidRequestValid(bid)).to.be.true; + }); + }); + + describe('buildRequests', () => { + const bidList = [bidItem]; + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests(bidList, bidRequest) + ); + const _data = request.data; + + it('should be create a request to server with POST method, data, valid url', () => { + expect(request).to.be.exist; + expect(_data).to.be.exist; + expect(request.method).to.be.exist; + expect(request.method).equal('POST'); + expect(request.url).to.be.exist; + expect(request.url).equal(ENDPOINT + eclickadsBidderConfigData.fosp_uid); + }); + it('should return valid data format if bid array is valid', () => { + expect(_data).to.be.an('object'); + expect(_data).to.has.all.keys( + 'deviceWidth', + 'deviceHeight', + 'language', + 'host', + 'ua', + 'page', + 'imp', + 'device', + 'myvne_id', + 'orig_aid', + 'fosp_aid', + 'fosp_uid', + 'id' + ); + expect(_data.deviceWidth).to.be.an('number'); + expect(_data.deviceHeight).to.be.an('number'); + expect(_data.device).to.be.an('string'); + expect(_data.language).to.be.an('string'); + expect(_data.host).to.be.an('string').that.is.equal('page.example.com'); + expect(_data.page) + .to.be.an('string') + .that.is.equal('https://page.example.com/here.html'); + expect(_data.imp).to.be.an('array'); + expect(_data.myvne_id).to.be.an('string'); + expect(_data.orig_aid).to.be.an('string'); + expect(_data.fosp_aid).to.be.an('string'); + expect(_data.myvne_id).to.be.an('string'); + expect(_data.fosp_uid).to.be.an('string'); + expect(_data.id).to.be.an('string'); + }); + + it('should return empty array if there is no bidItem passed', () => { + const request = config.runWithBidder(BIDDER_CODE, () => + spec.buildRequests([], bidRequest) + ); + const _data = request.data; + expect(_data.imp).to.be.an('array').that.is.empty; + }); + + it('should return the number of imp equal to the number of bidItem', () => { + expect(_data.imp).to.have.lengthOf(bidList.length); + }); + + it('have to contain required params and correct format for sending to EClickAds', () => { + const item = _data.imp[0]; + expect(item.zid).to.be.an('string'); + }); + }); + + describe('interpretResponse', () => { + const expectedResponse = { + id: '84b2a41c4299bb9b8924423e', + seat: '35809', + seatbid: [ + { + id: 'DBCCDFD5-AACC-424E-8225-4160D35CBE5D', + impid: '35ea1073c745d6c', + adUnitCode: '8871826dc92e', + requestId: '1122839202z3v', + creativeId: '112233ss921v', + netRevenue: true, + currency: ['VND'], + cpm: 0.1844, + ad: 'eclickads_ad_p', + }, + ], + }; + + const response = spec.interpretResponse({ body: expectedResponse }); + + it('should return an array of offers', () => { + expect(response).to.be.an('array'); + }); + + it('should return empty array if there is no offer from server response', () => { + const emptyOfferResponse = deepClone(expectedResponse); + emptyOfferResponse.seatbid = []; + const response = spec.interpretResponse({ body: emptyOfferResponse }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return empty array if seatbid from server response is null or missing', () => { + const nullOfferResponse = deepClone(expectedResponse); + nullOfferResponse.seatbid = null; + const response = spec.interpretResponse({ body: nullOfferResponse }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return empty array if server response is get error - empty', () => { + const response = spec.interpretResponse({ body: undefined }); + expect(response).to.be.an('array').that.is.empty; + }); + + it('should return correct format, params for each of offers from server response', () => { + const offer = response[0]; + expect(offer.id).to.be.an('string').that.is.not.empty; + expect(offer.impid).to.be.an('string').that.is.not.empty; + expect(offer.requestId).to.be.an('string').that.is.not.empty; + expect(offer.creativeId).to.be.an('string').that.is.not.empty; + expect(offer.netRevenue).to.be.an('boolean'); + expect(offer.ttl).to.be.an('number'); + expect(offer.cpm).to.be.an('number').greaterThan(0); + expect(offer.adserverTargeting).to.be.an('object'); + expect(offer.adserverTargeting['hb_ad_eclickads']).to.be.an('string').that + .is.not.empty; + }); + }); +}); diff --git a/test/spec/modules/edge226BidAdapter_spec.js b/test/spec/modules/edge226BidAdapter_spec.js index 4819d8d4a4e..e9e1c34b9cd 100644 --- a/test/spec/modules/edge226BidAdapter_spec.js +++ b/test/spec/modules/edge226BidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/edge226BidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'edge226' +const bidder = 'edge226'; describe('Edge226BidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('Edge226BidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('Edge226BidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -110,7 +134,9 @@ describe('Edge226BidAdapter', function () { it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', + expect(data).to.have.all.keys( + 'device', + 'deviceWidth', 'deviceHeight', 'language', 'secure', @@ -129,7 +155,7 @@ describe('Edge226BidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +171,56 @@ describe('Edge226BidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +246,10 @@ describe('Edge226BidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +264,38 @@ describe('Edge226BidAdapter', function () { 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); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { diff --git a/test/spec/modules/ehealthcaresolutionsBidAdapter_spec.js b/test/spec/modules/ehealthcaresolutionsBidAdapter_spec.js new file mode 100644 index 00000000000..c420c387598 --- /dev/null +++ b/test/spec/modules/ehealthcaresolutionsBidAdapter_spec.js @@ -0,0 +1,322 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/ehealthcaresolutionsBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('ehealthcaresolutions adapter', function () { + let bannerRequest, nativeRequest; + let bannerResponse, nativeResponse, invalidBannerResponse, invalidNativeResponse; + + beforeEach(function () { + bannerRequest = [ + { + bidder: 'ehealthcaresolutions', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + params: { + placement_id: 111520, + bid_floor: 0.5 + } + } + ]; + nativeRequest = [ + { + bidder: 'ehealthcaresolutions', + mediaTypes: { + native: { + title: { required: true, len: 100 }, + image: { required: true, sizes: [300, 250] }, + sponsored: { required: false }, + clickUrl: { required: true }, + desc: { required: true }, + icon: { required: false, sizes: [50, 50] }, + cta: { required: false } + } + }, + params: { + placement_id: 111519, + bid_floor: 1 + } + } + ]; + bannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.ehealthcaresolutions.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.ehealthcaresolutions.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.ehealthcaresolutions.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.ehealthcaresolutions.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'ehealthcaresolutions', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'ehealthcaresolutions', + params: { + placement_id: 111520 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'ehealthcaresolutions', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb.ehealthcaresolutions.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(111520); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb.ehealthcaresolutions.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(111519); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/eids_spec.js b/test/spec/modules/eids_spec.js index e1f2394ab27..30bfabb6f50 100644 --- a/test/spec/modules/eids_spec.js +++ b/test/spec/modules/eids_spec.js @@ -1,630 +1,7 @@ import {createEidsArray} from 'modules/userId/eids.js'; -import {expect} from 'chai'; -// Note: In unit tets cases for bidders, call the createEidsArray function over userId object that is used for calling fetchBids -// this way the request will stay consistent and unit test cases will not need lots of changes. - -describe('eids array generation for known sub-modules', function() { - it('pubCommonId', function() { - const userId = { - pubcid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubcid.org', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('unifiedId: ext generation', function() { - const userId = { - tdid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'some-random-id-value', atype: 1, ext: { rtiPartner: 'TDID' }}] - }); - }); - - it('unifiedId: ext generation with provider', function() { - const userId = { - tdid: {'id': 'some-sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'some-sample_id', atype: 1, ext: { rtiPartner: 'TDID', provider: 'some.provider.com' }}] - }); - }); - - 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 = { - parrableId: { - eid: 'some-random-id-value' - } - }; - 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}] - }); - }); - - it('merkleId (legacy) - supports single id', function() { - const userId = { - 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: 'merkleinc.com', - uids: [{ - id: 'some-random-id-value', - atype: 3, - ext: { keyID: 1 } - }] - }); - }); - - it('merkleId supports multiple source providers', function() { - const userId = { - merkleId: [{ - id: 'some-random-id-value', ext: { enc: 1, keyID: 16, idName: 'pamId', ssp: 'ssp1' } - }, { - id: 'another-random-id-value', - ext: { - enc: 1, - idName: 'pamId', - third: 4, - ssp: 'ssp2' - } - }] - } - - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(2); - expect(newEids[0]).to.deep.equal({ - source: 'ssp1.merkleinc.com', - uids: [{id: 'some-random-id-value', - atype: 3, - ext: { - enc: 1, - keyID: 16, - idName: 'pamId', - ssp: 'ssp1' - } - }] - }); - expect(newEids[1]).to.deep.equal({ - source: 'ssp2.merkleinc.com', - uids: [{id: 'another-random-id-value', - atype: 3, - ext: { - third: 4, - enc: 1, - idName: 'pamId', - ssp: 'ssp2' - } - }] - }); - }); - - it('identityLink', function() { - const userId = { - idl_env: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('liveIntentId; getValue call and ext', function() { - const userId = { - lipb: { - lipbid: 'some-random-id-value', - segments: ['s1', 's2'] - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 3}], - ext: {segments: ['s1', 's2']} - }); - }); - - it('bidswitch', function() { - const userId = { - bidswitch: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'bidswitch.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('bidswitch with ext', function() { - const userId = { - bidswitch: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'bidswitch.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('medianet', function() { - const userId = { - medianet: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'media.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('medianet with ext', function() { - const userId = { - medianet: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'media.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('sovrn', function() { - const userId = { - sovrn: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.sovrn.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('sovrn with ext', function() { - const userId = { - sovrn: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.sovrn.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('magnite', function() { - const userId = { - magnite: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'rubiconproject.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('magnite with ext', function() { - const userId = { - magnite: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'rubiconproject.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('index', function() { - const userId = { - index: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.indexexchange.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('index with ext', function() { - const userId = { - index: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.indexexchange.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('openx', function () { - const userId = { - openx: { 'id': 'sample_id' } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'openx.net', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('openx with ext', function () { - const userId = { - openx: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'openx.net', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('pubmatic', function() { - const userId = { - pubmatic: {'id': 'sample_id'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubmatic.com', - uids: [{ - id: 'sample_id', - atype: 3 - }] - }); - }); - - it('pubmatic with ext', function() { - const userId = { - pubmatic: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'pubmatic.com', - uids: [{ - id: 'sample_id', - atype: 3, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('liveIntentId; getValue call and NO ext', function() { - const userId = { - lipb: { - lipbid: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('britepoolId', function() { - const userId = { - britepoolid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'britepool.com', - uids: [{id: 'some-random-id-value', atype: 3}] - }); - }); - - it('lotamePanoramaId', function () { - const userId = { - lotamePanoramaId: 'some-random-id-value', - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'crwdcntrl.net', - uids: [{ id: 'some-random-id-value', atype: 1 }], - }); - }); - - it('criteo', function() { - const userId = { - criteoId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'criteo.com', - uids: [{id: 'some-random-id-value', atype: 1}] - }); - }); - - it('tapadId', function() { - const userId = { - tapadId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - 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' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'netid.de', - 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: 'zeotap.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('hadronId', function() { - const userId = { - hadronId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'audigent.com', - uids: [{ - 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('uid2 with ext', function() { - const userId = { - uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}} - }; - 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, - ext: { - provider: 'some.provider.com' - } - }] - }); - }); - - it('euid', function() { - const userId = { - euid: {'id': 'Sample_AD_Token'} - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'euid.eu', - 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('tncid', function() { - const userId = { - tncid: 'TEST_TNCID' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'thenewco.it', - uids: [{ - id: 'TEST_TNCID', - atype: 3 - }] - }); - }); - - it('pubProvidedId', function() { +describe('eids array generation for known sub-modules', function () { + it('pubProvidedId', function () { const userId = { pubProvidedId: [{ source: 'example.com', @@ -659,148 +36,10 @@ describe('eids array generation for known sub-modules', function() { }] }); }); - - it('amxId', () => { - const id = 'c4bcadb0-124f-4468-a91a-d3d44cf311c5' - const userId = { - amxId: id - }; - - const [eid] = createEidsArray(userId); - expect(eid).to.deep.equal({ - source: 'amxdt.net', - uids: [{ - atype: 1, - id, - }] - }); - }); - - it('qid', function() { - const userId = { - qid: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'adquery.io', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('operaId', function() { - const userId = { - operaId: 'some-random-id-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 't.adx.opera.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('33acrossId', function() { - const userId = { - '33acrossId': { - envelope: 'some-random-id-value' - } - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: '33across.com', - uids: [{ - id: 'some-random-id-value', - atype: 1 - }] - }); - }); - - it('czechAdId', () => { - const id = 'some-random-id-value' - const userId = { czechAdId: id }; - const [eid] = createEidsArray(userId); - expect(eid).to.deep.equal({ - source: 'czechadid.cz', - uids: [{ id: 'some-random-id-value', atype: 1 }] - }); - }); - - describe('ftrackId', () => { - it('should return the correct EID schema', () => { - // This is the schema returned from the ftrack decode() method - expect(createEidsArray({ - ftrackId: { - uid: 'test-device-id', - ext: { - DeviceID: 'test-device-id', - SingleDeviceID: 'test-single-device-id', - HHID: 'test-household-id' - } - }, - foo: { - bar: 'baz' - }, - lorem: { - ipsum: '' - } - })).to.deep.equal([{ - source: 'flashtalking.com', - uids: [{ - atype: 1, - id: 'test-device-id', - ext: { - DeviceID: 'test-device-id', - SingleDeviceID: 'test-single-device-id', - HHID: 'test-household-id' - } - }] - }]); - }); - }); - - describe('imuid', function() { - it('should return the correct EID schema with imuid', function() { - const userId = { - imuid: 'testimuid' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'intimatemerger.com', - uids: [{ - id: 'testimuid', - atype: 1 - }] - }); - }); - - it('should return the correct EID schema with imppid', function() { - const userId = { - imppid: 'imppid-value-imppid-value-imppid-value' - }; - const newEids = createEidsArray(userId); - expect(newEids.length).to.equal(1); - expect(newEids[0]).to.deep.equal({ - source: 'ppid.intimatemerger.com', - uids: [{ - id: 'imppid-value-imppid-value-imppid-value', - atype: 1 - }] - }); - }); - }); }); describe('Negative case', function () { - it('eids array generation for UN-known sub-module', function() { + it('eids array generation for UN-known sub-module', function () { // UnknownCommonId const userId = { unknowncid: 'some-random-id-value' @@ -809,7 +48,7 @@ describe('Negative case', function () { expect(newEids.length).to.equal(0); }); - it('eids array generation for known sub-module with non-string value', function() { + it('eids array generation for known sub-module with non-string value', function () { // pubCommonId let userId = { pubcid: undefined diff --git a/test/spec/modules/eightPodAnalyticsAdapter_spec.js b/test/spec/modules/eightPodAnalyticsAdapter_spec.js index 930b15bda31..55a09db03b4 100644 --- a/test/spec/modules/eightPodAnalyticsAdapter_spec.js +++ b/test/spec/modules/eightPodAnalyticsAdapter_spec.js @@ -1,4 +1,4 @@ -import analyticsAdapter, { storage, queue, context, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; +import analyticsAdapter, { storage, queue, trackEvent } from 'modules/eightPodAnalyticsAdapter.js'; import { expect } from 'chai'; import adapterManager from 'src/adapterManager.js'; import eightPodAnalytics from 'modules/eightPodAnalyticsAdapter.js'; @@ -45,11 +45,12 @@ describe('eightPodAnalyticAdapter', function() { it('should subscribe on messageEvents', function() { getDataFromLocalStorageStub.returns(JSON.stringify([])); sandbox.spy(eightPodAnalytics, 'eventSubscribe'); + sandbox.spy(eightPodAnalytics, 'getEventFromLocalStorage'); analyticsAdapter.setupPage(); - sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 1); - sandbox.assert.callCount(addEventListenerSpy, 1); + sandbox.assert.callCount(analyticsAdapter.eventSubscribe, 0); + sandbox.assert.callCount(analyticsAdapter.getEventFromLocalStorage, 1); }); it('should receive saved events list', function() { @@ -67,6 +68,7 @@ describe('eightPodAnalyticAdapter', function() { beforeEach(function() { setupPageStub = sandbox.stub(eightPodAnalytics, 'setupPage'); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -79,16 +81,18 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 0); - expect(context).to.deep.equal(undefined) + expect(analyticsAdapter.getContext()).to.deep.equal({}) }); it('should call setup page and get context', function() { eightPodAnalytics.track({ eventType: BID_WON, args: { + adUnitCode: 'adUnitCode', bidder: 'eightPod', creativeId: 'creativeId', seatBidId: 'seatBidId', + cid: 'campaignId', params: [ { publisherId: 'publisherId', @@ -99,23 +103,27 @@ describe('eightPodAnalyticAdapter', function() { }) sandbox.assert.callCount(setupPageStub, 1); - expect(context).to.deep.equal({ - bidId: 'seatBidId', - campaignId: 'campaignId', - placementId: 'placementId', - publisherId: 'publisherId', - variantId: 'creativeId' + expect(analyticsAdapter.getContext()).to.deep.equal({ + adUnitCode: { + bidId: 'seatBidId', + campaignId: 'campaignId', + placementId: 'placementId', + publisherId: 'publisherId', + variantId: 'creativeId' + } }) }); }); describe('trackEvent', function() { let getContextStub, getTimeStub; + const adUnitCode = 'adUnitCode'; beforeEach(function() { getContextStub = sandbox.stub(eightPodAnalytics, 'getContext'); getTimeStub = sandbox.stub(Date.prototype, 'getTime').returns(1234); eightPodAnalytics.resetQueue(); + eightPodAnalytics.resetContext(); }); afterEach(function() { @@ -124,7 +132,7 @@ describe('eightPodAnalyticAdapter', function() { }); it('should add event to the queue', function() { - getContextStub.returns({}); + getContextStub.returns({adUnitCode: {}}); const event1 = { detail: { @@ -169,9 +177,10 @@ describe('eightPodAnalyticAdapter', function() { value: 2 } } - trackEvent(event1) + + trackEvent(event1, adUnitCode) expect(queue).to.deep.equal([result1]); - trackEvent(event2); + trackEvent(event2, adUnitCode); expect(queue).to.deep.equal([result1, result2]); }); }); diff --git a/test/spec/modules/emtvBidAdapter_spec.js b/test/spec/modules/emtvBidAdapter_spec.js index 4f95a0cc094..ce81dc15ad4 100644 --- a/test/spec/modules/emtvBidAdapter_spec.js +++ b/test/spec/modules/emtvBidAdapter_spec.js @@ -8,6 +8,16 @@ const adUrl = 'https://us-east-ep.engagemedia.tv/pbjs'; const syncUrl = 'https://cs.engagemedia.tv'; describe('EMTVBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('EMTVBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,10 +88,22 @@ describe('EMTVBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -113,6 +138,7 @@ describe('EMTVBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -130,7 +156,7 @@ describe('EMTVBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -146,6 +172,56 @@ describe('EMTVBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -171,8 +247,10 @@ describe('EMTVBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -187,12 +265,38 @@ describe('EMTVBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -396,5 +500,17 @@ describe('EMTVBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); }); }); diff --git a/test/spec/modules/eplanningAnalyticsAdapter_spec.js b/test/spec/modules/eplanningAnalyticsAdapter_spec.js deleted file mode 100644 index dddc248b409..00000000000 --- a/test/spec/modules/eplanningAnalyticsAdapter_spec.js +++ /dev/null @@ -1,164 +0,0 @@ -import eplAnalyticsAdapter from 'modules/eplanningAnalyticsAdapter.js'; -import {includes} from 'src/polyfill.js'; -import { expect } from 'chai'; -import { parseUrl } from 'src/utils.js'; -import { server } from 'test/mocks/xhr.js'; -import { EVENTS } from 'src/constants.js'; - -let adapterManager = require('src/adapterManager').default; -let events = require('src/events'); - -describe('eplanning analytics adapter', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - events.getEvents.restore(); - eplAnalyticsAdapter.disableAnalytics(); - }); - - describe('track', function () { - it('builds and sends auction data', function () { - sinon.spy(eplAnalyticsAdapter, 'track'); - - let auctionTimestamp = 1496510254313; - let pauctionId = '5018eb39-f900-4370-b71e-3bb5b48d324f'; - let initOptions = { - host: 'https://ads.ar.e-planning.net/hba/1/', - ci: '12345' - }; - let pbidderCode = 'adapter'; - - const bidRequest = { - bidderCode: pbidderCode, - auctionId: pauctionId, - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: pbidderCode, - placementCode: 'container-1', - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: pauctionId, - startTime: 1509369418389, - sizes: [[300, 250]], - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }; - - const bidResponse = { - bidderCode: pbidderCode, - adId: '208750227436c1', - cpm: 0.015, - auctionId: pauctionId, - responseTimestamp: 1509369418832, - requestTimestamp: 1509369418389, - bidder: pbidderCode, - timeToRespond: 443, - size: '300x250', - width: 300, - height: 250, - }; - - let bidTimeout = [ - { - bidId: '208750227436c1', - bidder: pbidderCode, - auctionId: pauctionId - } - ]; - - adapterManager.registerAnalyticsAdapter({ - code: 'eplanning', - adapter: eplAnalyticsAdapter - }); - - adapterManager.enableAnalytics({ - provider: 'eplanning', - options: initOptions - }); - - // Emit the events with the "real" arguments - - // Step 1: Send auction init event - events.emit(EVENTS.AUCTION_INIT, { - auctionId: pauctionId, - timestamp: auctionTimestamp - }); - - // Step 2: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, bidRequest); - - // Step 3: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bidResponse); - - // Step 4: Send bid time out event - events.emit(EVENTS.BID_TIMEOUT, bidTimeout); - - // Step 5: Send auction bid won event - events.emit(EVENTS.BID_WON, { - adId: 'adIdData', - ad: 'adContent', - auctionId: pauctionId, - width: 300, - height: 250 - }); - - // Step 6: Send auction end event - events.emit(EVENTS.AUCTION_END, { auctionId: pauctionId }); - - // Step 7: Find the request data sent (filtering other hosts) - let requests = server.requests.filter(req => { - return req.url.indexOf(initOptions.host) > -1; - }); - expect(requests.length).to.equal(1); - - expect(includes([initOptions.host + initOptions.ci], requests[0].url)); - expect(includes(['https://ads.ar.e-planning.net/hba/1/12345?d='], requests[0].url)); - - let info = requests[0].url; - let purl = parseUrl(info); - let eplData = JSON.parse(decodeURIComponent(purl.search.d)); - - // Step 8 check that 6 events were sent - expect(eplData.length).to.equal(6); - - // Step 9 verify that we only receive the parameters we need - let expectedEventValues = [ - // AUCTION INIT - { - ec: EVENTS.AUCTION_INIT, - p: {auctionId: pauctionId, time: auctionTimestamp}}, - // BID REQ - { - ec: EVENTS.BID_REQUESTED, - p: {auctionId: pauctionId, time: 1509369418389, bidder: pbidderCode, bids: [{time: 1509369418389, sizes: [[300, 250]], bidder: pbidderCode, placementCode: 'container-1', auctionId: pauctionId}]}}, - // BID RESP - { - ec: EVENTS.BID_RESPONSE, - p: {auctionId: pauctionId, bidder: pbidderCode, cpm: 0.015, size: '300x250', time: 1509369418832}}, - // BID T.O. - { - ec: EVENTS.BID_TIMEOUT, - p: [{auctionId: pauctionId, bidder: pbidderCode}]}, - // BID WON - { - ec: EVENTS.BID_WON, - p: {auctionId: pauctionId, size: '300x250'}}, - // AUCTION END - { - ec: EVENTS.AUCTION_END, - p: {auctionId: pauctionId}} - ]; - - for (let evid = 0; evid < eplData.length; evid++) { - expect(eplData[evid]).to.deep.equal(expectedEventValues[evid]); - } - - // Step 10 check that the host to send the ajax request is configurable via options - expect(eplAnalyticsAdapter.context.host).to.equal(initOptions.host); - }); - }); -}); diff --git a/test/spec/modules/equativBidAdapter_spec.js b/test/spec/modules/equativBidAdapter_spec.js new file mode 100644 index 00000000000..c52507a000b --- /dev/null +++ b/test/spec/modules/equativBidAdapter_spec.js @@ -0,0 +1,536 @@ +import { BANNER } from 'src/mediaTypes.js'; +import { getBidFloor } from 'libraries/equativUtils/equativUtils.js' +import { converter, spec, storage } from 'modules/equativBidAdapter.js'; + +describe('Equativ bid adapter tests', () => { + const DEFAULT_BID_REQUESTS = [ + { + adUnitCode: 'eqtv_42', + bidId: 'abcd1234', + mediaTypes: { + banner: { + sizes: [ + [300, 250], + [300, 600], + ], + }, + }, + bidder: 'equativ', + params: { + networkId: 111, + }, + requestId: 'efgh5678', + ortb2Imp: { + ext: { + tid: 'zsfgzzg', + }, + }, + }, + ]; + + const DEFAULT_BIDDER_REQUEST = { + bidderCode: 'equativ', + bids: DEFAULT_BID_REQUESTS, + }; + + const SAMPLE_RESPONSE = { + body: { + id: '12h712u7-k22g-8124-ab7a-h268s22dy271', + seatbid: [ + { + bid: [ + { + id: '1bh7jku7-ko2g-8654-ab72-h268shvwy271', + impid: 'r12gwgf231', + price: 0.6565, + adm: '

AD

', + adomain: ['abc.com'], + cid: '1242512', + crid: '535231', + w: 300, + h: 600, + mtype: 1, + cat: ['IAB19', 'IAB19-1'], + cattax: 1, + }, + ], + seat: '4212', + }, + ], + cur: 'USD', + statuscode: 0, + }, + }; + + // const RESPONSE_WITH_DSP_PIXELS = { + // ...SAMPLE_RESPONSE, + // body: { + // dspPixels: ['1st-pixel', '2nd-pixel', '3rd-pixel'] + // } + // }; + + describe('buildRequests', () => { + it('should build correct request using ORTB converter', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + const dataFromConverter = converter.toORTB({ + bidderRequest: DEFAULT_BIDDER_REQUEST, + bidRequests: DEFAULT_BID_REQUESTS, + }); + expect(request).to.deep.equal({ + data: { ...dataFromConverter, id: request.data.id }, + method: 'POST', + url: 'https://ssb-global.smartadserver.com/api/bid?callerId=169', + }); + }); + + it('should add ext.bidder to imp object when siteId is defined', () => { + const bidRequests = [ + { ...DEFAULT_BID_REQUESTS[0], params: { siteId: 123 } }, + ]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.bidder).to.deep.equal({ + siteId: 123, + }); + }); + + it('should add ext.bidder to imp object when pageId is defined', () => { + const bidRequests = [ + { ...DEFAULT_BID_REQUESTS[0], params: { pageId: 123 } }, + ]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.bidder).to.deep.equal({ + pageId: 123, + }); + }); + + it('should add ext.bidder to imp object when formatId is defined', () => { + const bidRequests = [ + { ...DEFAULT_BID_REQUESTS[0], params: { formatId: 123 } }, + ]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.bidder).to.deep.equal({ + formatId: 123, + }); + }); + + it('should not add ext.bidder to imp object when siteId, pageId, formatId are not defined', () => { + const bidRequests = [{ ...DEFAULT_BID_REQUESTS[0], params: {} }]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.bidder).to.be.undefined; + }); + + it('should add site.publisher.id param', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + expect(request.data.site.publisher.id).to.equal(111); + }); + + it('should pass ortb2.site.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + site: { + publisher: { + id: 98, + } + } + } + }]; + delete bidRequests[0].params; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site.publisher.id).to.equal(98); + }); + + it('should pass networkId as site.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + site: { + publisher: {} + } + } + }]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site.publisher.id).to.equal(111); + }); + + it('should pass ortb2.app.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + app: { + publisher: { + id: 27, + } + } + } + }]; + delete bidRequests[0].params; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.app.publisher.id).to.equal(27); + }); + + it('should pass networkId as app.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + app: { + publisher: {} + } + } + }]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.app.publisher.id).to.equal(111); + }); + + it('should pass ortb2.dooh.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + dooh: { + publisher: { + id: 35, + } + } + } + }]; + delete bidRequests[0].params; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.dooh.publisher.id).to.equal(35); + }); + + it('should pass networkId as dooh.publisher.id', () => { + const bidRequests = [{ + ...DEFAULT_BID_REQUESTS[0], + ortb2: { + dooh: { + publisher: {} + } + } + }]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.dooh.publisher.id).to.equal(111); + }); + + it('should send default floor of 0.0', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + expect(request.data.imp[0]).to.have.property('bidfloor').that.eq(0.0); + }); + + it('should send secure connection', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + expect(request.data.imp[0]).to.have.property('secure').that.eq(1); + }); + + it('should have tagid', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + expect(request.data.imp[0]).to.have.property('tagid').that.eq(DEFAULT_BID_REQUESTS[0].adUnitCode); + }); + + it('should remove dt', () => { + const bidRequests = [ + { ...DEFAULT_BID_REQUESTS[0], ortb2Imp: { dt: 1728377558235 } } + ]; + const bidderRequest = { ...DEFAULT_BIDDER_REQUEST, bids: bidRequests }; + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0]).to.not.have.property('dt'); + }); + + it('should read and send pid as buyeruid', () => { + const cookieData = { + 'eqt_pid': '7789746781' + }; + const getCookieStub = sinon.stub(storage, 'getCookie'); + getCookieStub.callsFake(cookieName => cookieData[cookieName]); + + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + + expect(request.data.user).to.have.property('buyeruid').that.eq(cookieData['eqt_pid']); + + getCookieStub.restore(); + }); + + it('should not send buyeruid', () => { + const getCookieStub = sinon.stub(storage, 'getCookie'); + getCookieStub.callsFake(() => null); + + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + + expect(request.data).to.not.have.property('user'); + + getCookieStub.restore(); + }); + + it('should pass buyeruid defined in config', () => { + const getCookieStub = sinon.stub(storage, 'getCookie'); + getCookieStub.callsFake(() => undefined); + + const bidRequest = { + ...DEFAULT_BIDDER_REQUEST, + ortb2: { + user: { + buyeruid: 'buyeruid-provided-by-publisher' + } + } + }; + const request = spec.buildRequests([ DEFAULT_BID_REQUESTS[0] ], bidRequest); + + expect(request.data.user.buyeruid).to.deep.eq(bidRequest.ortb2.user.buyeruid); + + getCookieStub.restore(); + }); + }); + + describe('getBidFloor', () => { + it('should return floor of 0.0 if floor module not available', () => { + const bid = { + ...DEFAULT_BID_REQUESTS[0], + getFloor: false, + }; + expect(getBidFloor(bid)).to.deep.eq(0.0); + }); + + it('should return floor of 0.0 if mediaTypes not defined', () => { + const bid = { + getFloor: () => ({}) + }; + expect(bid.mediaTypes).to.be.undefined; + expect(getBidFloor(bid)).to.deep.eq(0.0); + }); + + it('should return proper min floor', () => { + const bid = { + ...DEFAULT_BID_REQUESTS[0], + getFloor: data => { + if (data.size[0] === 300 && data.size[1] === 250) { + return { floor: 1.13 }; + } else if (data.size[0] === 300 && data.size[1] === 600) { + return { floor: 1.39 }; + } else { + return { floor: 0.52 }; + } + } + }; + expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(1.13); + }); + + it('should return global media type floor if no rule for size', () => { + const bid = { + ...DEFAULT_BID_REQUESTS[0], + getFloor: data => { + if (data.size[0] === 728 && data.size[1] === 90) { + return { floor: 1.13 }; + } else if (data.size[0] === 300 && data.size[1] === 600) { + return { floor: 1.36 }; + } else { + return { floor: 0.34 }; + } + } + }; + expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(0.34); + }); + + it('should return floor of 0 if no rule for size', () => { + const bid = { + ...DEFAULT_BID_REQUESTS[0], + getFloor: data => { + if (data.size[0] === 728 && data.size[1] === 90) { + return { floor: 1.13 }; + } else if (data.size[0] === 300 && data.size[1] === 600) { + return { floor: 1.36 }; + } else { + return {}; + } + } + }; + expect(getBidFloor(bid, 'USD', BANNER)).to.deep.eq(0.0); + }); + }); + + describe('getUserSyncs', () => { + let setCookieStub; + + beforeEach(() => setCookieStub = sinon.stub(storage, 'setCookie')); + + afterEach(() => setCookieStub.restore()); + + it('should return empty array if iframe sync not enabled', () => { + const syncs = spec.getUserSyncs({}, SAMPLE_RESPONSE); + expect(syncs).to.deep.equal([]); + }); + + it('should retrieve and save user pid', (done) => { + const userSyncs = spec.getUserSyncs( + { iframeEnabled: true }, + SAMPLE_RESPONSE + ); + + window.dispatchEvent(new MessageEvent('message', { + data: { + pid: '7767825890726' + }, + origin: 'https://apps.smartadserver.com' + })); + + const exp = new Date(); + exp.setTime(Date.now() + 31536000000); + + setTimeout(() => { + expect(setCookieStub.calledOnce).to.be.true; + expect(setCookieStub.calledWith('eqt_pid', '7767825890726', exp.toUTCString())).to.be.true; + done(); + }); + }); + + it('should not save user pid coming from not origin', (done) => { + const userSyncs = spec.getUserSyncs( + { iframeEnabled: true }, + SAMPLE_RESPONSE + ); + + window.dispatchEvent(new MessageEvent('message', { + data: { + pid: '7767825890726' + }, + origin: 'https://another-origin.com' + })); + + setTimeout(() => { + expect(setCookieStub.notCalled).to.be.true; + done(); + }); + }); + + it('should not save empty pid', (done) => { + const userSyncs = spec.getUserSyncs( + { iframeEnabled: true }, + SAMPLE_RESPONSE + ); + + window.dispatchEvent(new MessageEvent('message', { + data: { + pid: '' + }, + origin: 'https://apps.smartadserver.com' + })); + + setTimeout(() => { + expect(setCookieStub.notCalled).to.be.true; + done(); + }); + }); + + it('should return array including iframe cookie sync object', () => { + const syncs = spec.getUserSyncs( + { iframeEnabled: true }, + SAMPLE_RESPONSE + ); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0]).to.deep.equal({ + type: 'iframe', + url: 'https://apps.smartadserver.com/diff/templates/asset/csync.html' + }); + }); + }); + + describe('interpretResponse', () => { + it('should return data returned by ORTB converter', () => { + const request = spec.buildRequests( + DEFAULT_BID_REQUESTS, + DEFAULT_BIDDER_REQUEST + ); + const bids = spec.interpretResponse(SAMPLE_RESPONSE, request); + expect(bids).to.deep.equal( + converter.fromORTB({ + request: request.data, + response: SAMPLE_RESPONSE.body, + }) + ); + }); + }); + + describe('isBidRequestValid', () => { + it('should return true if params.networkId is set', () => { + const bidRequest = { + params: { + networkId: 123, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true if ortb2.site.publisher.id is set', () => { + const bidRequest = { + ortb2: { + site: { + publisher: { + id: 123, + }, + }, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true if ortb2.app.publisher.id is set', () => { + const bidRequest = { + ortb2: { + app: { + publisher: { + id: 123, + }, + }, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return true if ortb2.dooh.publisher.id is set', () => { + const bidRequest = { + ortb2: { + dooh: { + publisher: { + id: 123, + }, + }, + }, + }; + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + + it('should return false if networkId is not set', () => { + const bidRequest = {}; + expect(spec.isBidRequestValid(bidRequest)).to.equal(false); + }); + }); +}); diff --git a/test/spec/modules/eskimiBidAdapter_spec.js b/test/spec/modules/eskimiBidAdapter_spec.js index d01240c86ab..a452f115767 100644 --- a/test/spec/modules/eskimiBidAdapter_spec.js +++ b/test/spec/modules/eskimiBidAdapter_spec.js @@ -1,5 +1,5 @@ -import { expect } from 'chai'; -import { spec } from 'modules/eskimiBidAdapter.js'; +import {expect} from 'chai'; +import {spec} from 'modules/eskimiBidAdapter.js'; import * as utils from 'src/utils'; const BANNER_BID = { @@ -33,7 +33,7 @@ const VIDEO_BID = { playbackmethod: [2, 4, 6], playerSize: [[1024, 768]], protocols: [3, 4, 7, 8, 10], - placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, startdelay: 0 @@ -165,8 +165,8 @@ describe('Eskimi bid adapter', function () { it('should properly forward ORTB blocking params', function () { let bid = utils.deepClone(BANNER_BID); bid = utils.mergeDeep(bid, { - params: { bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example'] }, - mediaTypes: { banner: { battr: [1] } } + params: {bcat: ['IAB1-1'], badv: ['example.com'], bapp: ['com.example']}, + mediaTypes: {banner: {battr: [1]}} }); let [request] = spec.buildRequests([bid], BIDDER_REQUEST); @@ -222,7 +222,7 @@ describe('Eskimi bid adapter', function () { mimes: ['video/mp4', 'video/x-flv'], playbackmethod: [3, 4], protocols: [5, 6], - placement: 1, + plcmt: 1, minduration: 0, maxduration: 60, w: 1024, @@ -253,7 +253,7 @@ describe('Eskimi bid adapter', function () { const [request] = spec.buildRequests([bid], BIDDER_REQUEST); const response = utils.deepClone(BANNER_BID_RESPONSE); - const bids = spec.interpretResponse({ body: response }, request); + const bids = spec.interpretResponse({body: response}, request); expect(bids).to.be.an('array').that.is.not.empty; expect(bids[0].mediaType).to.equal('banner'); @@ -274,7 +274,7 @@ describe('Eskimi bid adapter', function () { const bid = utils.deepClone(BANNER_BID); let request = spec.buildRequests([bid], BIDDER_REQUEST)[0]; - const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, { 'body': {} }); + const EMPTY_RESP = Object.assign({}, BANNER_BID_RESPONSE, {'body': {}}); const bids = spec.interpretResponse(EMPTY_RESP, request); expect(bids).to.be.empty; }); @@ -285,7 +285,7 @@ describe('Eskimi bid adapter', function () { const bid = utils.deepClone(VIDEO_BID); const [request] = spec.buildRequests([bid], BIDDER_REQUEST); - const bids = spec.interpretResponse({ body: VIDEO_BID_RESPONSE }, request); + const bids = spec.interpretResponse({body: VIDEO_BID_RESPONSE}, request); expect(bids).to.be.an('array').that.is.not.empty; expect(bids[0].mediaType).to.equal('video'); diff --git a/test/spec/modules/euidIdSystem_spec.js b/test/spec/modules/euidIdSystem_spec.js index 9ad2b69e89c..aff1e0535ae 100644 --- a/test/spec/modules/euidIdSystem_spec.js +++ b/test/spec/modules/euidIdSystem_spec.js @@ -1,13 +1,14 @@ -import {coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; +import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import {euidIdSubmodule} from 'modules/euidIdSystem.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'src/prebid.js'; import * as utils from 'src/utils.js'; import {apiHelpers, cookieHelpers, runAuction, setGdprApplies} from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {server} from 'test/mocks/xhr'; +import {createEidsArray} from '../../../modules/userId/eids.js'; let expect = require('chai').expect; @@ -49,7 +50,7 @@ describe('EUID module', function() { const configureEuidCstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); before(function() { - uninstallGdprEnforcement(); + uninstallTcfControl(); hook.ready(); suiteSandbox = sinon.sandbox.create(); if (typeof window.crypto.subtle === 'undefined') { @@ -161,4 +162,24 @@ describe('EUID module', function() { expectOptout(bid, optoutToken); }); } + + describe('eid', () => { + before(() => { + attachIdSystem(euidIdSubmodule); + }); + it('euid', function() { + const userId = { + euid: {'id': 'Sample_AD_Token'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'euid.eu', + uids: [{ + id: 'Sample_AD_Token', + atype: 3 + }] + }); + }); + }) }); diff --git a/test/spec/modules/minutemediaplusBidAdapter_spec.js b/test/spec/modules/excoBidAdapter_spec.js similarity index 87% rename from test/spec/modules/minutemediaplusBidAdapter_spec.js rename to test/spec/modules/excoBidAdapter_spec.js index 5101f015b0e..39844f0bc6a 100644 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ b/test/spec/modules/excoBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/excoBidAdapter'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,16 +18,11 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/minutemediaplusBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; -const SUB_DOMAIN = 'exchange'; +const SUB_DOMAIN = 'rtb'; const BID = { 'bidId': '2d52001cabd527', @@ -37,7 +40,6 @@ const BID = { 'placementCode': 'div-gpt-ad-1460505748561-0', 'sizes': [[300, 250], [300, 600]], 'bidderRequestId': '1fdb5ff1b6eaa7', - 'auctionId': 'auction_id', 'bidRequestsCount': 4, 'bidderRequestsCount': 3, 'bidderWinsCount': 1, @@ -46,8 +48,8 @@ const BID = { 'mediaTypes': [BANNER], 'ortb2Imp': { 'ext': { - 'gpid': '1234567890', - tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' } } }; @@ -56,15 +58,9 @@ const VIDEO_BID = { 'bidId': '2d52001cabd527', 'adUnitCode': '63550ad1ff6642d368cba59dh5884270560', 'bidderRequestId': '12a8ae9ada9c13', - 'auctionId': 'auction_id', 'bidRequestsCount': 4, 'bidderRequestsCount': 3, 'bidderWinsCount': 1, - ortb2Imp: { - ext: { - tid: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - } - }, 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', 'params': { 'subDomain': SUB_DOMAIN, @@ -89,6 +85,12 @@ const VIDEO_BID = { 'api': [2], 'placement': 1 } + }, + 'ortb2Imp': { + 'ext': { + 'gpid': '0123456789', + 'tid': '56e184c6-bde9-497b-b9b9-cf47a61381ee' + } } } @@ -105,9 +107,15 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -127,7 +135,7 @@ const BIDDER_REQUEST = { 'architecture': '' } } - }, + } }; const SERVER_RESPONSE = { @@ -157,7 +165,7 @@ const VIDEO_SERVER_RESPONSE = { 'cid': '635509f7ff6642d368cb9837', 'results': [{ 'ad': '', - 'advertiserDomains': ['minutemedia-prebid.com'], + 'advertiserDomains': ['exco.com'], 'exp': 60, 'width': 545, 'height': 307, @@ -186,7 +194,7 @@ function getTopWindowQueryParams() { } } -describe('MinuteMediaPlus Bid Adapter', function () { +describe('ExcoBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); @@ -248,7 +256,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { let sandbox; before(function () { $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { + exco: { storageAllowed: true } }; @@ -259,7 +267,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build video request', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); config.setConfig({ - bidderTimeout: 3000 + bidderTimeout: 3000, + enableTIDs: true }); const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -278,9 +287,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { usPrivacy: 'consent_string', gppString: 'gpp_string', gppSid: [7], - prebidVersion: version, transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - auctionId: 'auction_id', + prebidVersion: version, bidRequestsCount: 4, bidderRequestsCount: 3, bidderWinsCount: 1, @@ -326,7 +334,14 @@ describe('MinuteMediaPlus Bid Adapter', function () { startdelay: 0 } }, - gpid: '' + gpid: '0123456789', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -334,7 +349,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should build banner request for each size', function () { const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); config.setConfig({ - bidderTimeout: 3000 + bidderTimeout: 3000, + enableTIDs: true }); const requests = adapter.buildRequests([BID], BIDDER_REQUEST); expect(requests).to.have.length(1); @@ -347,13 +363,12 @@ describe('MinuteMediaPlus Bid Adapter', function () { gppString: 'gpp_string', gppSid: [7], usPrivacy: 'consent_string', - transactionId: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', - auctionId: 'auction_id', bidRequestsCount: 4, bidderRequestsCount: 3, bidderWinsCount: 1, bidderTimeout: 3000, bidderRequestId: '1fdb5ff1b6eaa7', + transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', sizes: ['300x250', '300x600'], sua: { 'source': 2, @@ -384,10 +399,17 @@ describe('MinuteMediaPlus Bid Adapter', function () { schain: BID.schain, res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], - gpid: '1234567890', + gpid: '0123456789', uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -403,7 +425,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' }]); }); @@ -411,7 +433,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { const result = adapter.getUserSyncs({iframeEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ type: 'iframe', - url: 'https://sync.minutemedia-prebid.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' + url: 'https://cs.exco-pb.com/api/sync/iframe/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=' }]); }); @@ -419,29 +441,10 @@ describe('MinuteMediaPlus Bid Adapter', function () { const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE]); expect(result).to.deep.equal([{ - 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', + 'url': 'https://cs.exco-pb.com/api/sync/image/?cid=testcid123&gdpr=0&gdpr_consent=&us_privacy=', 'type': 'image' }]); }) - - it('should generate url with consent data', function () { - const gdprConsent = { - gdprApplies: true, - consentString: 'consent_string' - }; - const uspConsent = 'usp_string'; - const gppConsent = { - gppString: 'gpp_string', - applicableSections: [7] - } - - const result = adapter.getUserSyncs({pixelEnabled: true}, [SERVER_RESPONSE], gdprConsent, uspConsent, gppConsent); - - expect(result).to.deep.equal([{ - 'url': 'https://sync.minutemedia-prebid.com/api/sync/image/?cid=testcid123&gdpr=1&gdpr_consent=consent_string&us_privacy=usp_string&gpp=gpp_string&gpp_sid=7', - 'type': 'image' - }]); - }); }); describe('interpret response', function () { @@ -482,12 +485,12 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should get meta from response metaData', function () { const serverResponse = utils.deepClone(SERVER_RESPONSE); serverResponse.body.results[0].metaData = { - advertiserDomains: ['minutemedia-prebid.com'], + advertiserDomains: ['exco.com'], agencyName: 'Agency Name', }; const responses = adapter.interpretResponse(serverResponse, REQUEST); expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['minutemedia-prebid.com'], + advertiserDomains: ['exco.com'], agencyName: 'Agency Name' }); }); @@ -507,7 +510,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { ttl: 60, vastXml: '', meta: { - advertiserDomains: ['minutemedia-prebid.com'] + advertiserDomains: ['exco.com'] } }); }); @@ -530,8 +533,6 @@ describe('MinuteMediaPlus Bid Adapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -561,9 +562,9 @@ describe('MinuteMediaPlus Bid Adapter', function () { }); it('should return value when param supported', function () { - const cid = extractCID({'cID': '1'}); - const pid = extractPID({'Pid': '2'}); - const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); + 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'); @@ -573,7 +574,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { describe('unique deal id', function () { before(function () { $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { + exco: { storageAllowed: true } }; @@ -584,13 +585,13 @@ describe('MinuteMediaPlus Bid Adapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -598,7 +599,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -608,7 +609,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { describe('storage utils', function () { before(function () { $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { + exco: { storageAllowed: true } }; @@ -622,8 +623,8 @@ describe('MinuteMediaPlus Bid Adapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -634,7 +635,7 @@ describe('MinuteMediaPlus Bid Adapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/fanAdapter_spec.js b/test/spec/modules/fanAdapter_spec.js new file mode 100644 index 00000000000..010ba91339d --- /dev/null +++ b/test/spec/modules/fanAdapter_spec.js @@ -0,0 +1,315 @@ +import * as ajax from 'src/ajax.js'; +import { expect } from 'chai'; +import { spec } from 'modules/fanAdapter.js'; +import { newBidder } from 'src/adapters/bidderFactory.js'; +import { BANNER, NATIVE } from 'src/mediaTypes.js'; + +describe('Freedom Ad Network 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: { foo: 'bar' } + })).to.be.false; + }); + + it('valid bid should return true', function () { + expect(spec.isBidRequestValid({ + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + })).to.be.true; + }); + }); + + describe('Test buildRequests', function () { + const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: Date.now(), + bidderCode: 'myBidderCode', + bidderRequestId: '15246a574e859f', + refererInfo: { + page: '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: '8064026a1776', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + }, + { + adUnitCode: 'test-native', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1777', + bidder: 'freedomadnetwork', + 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: '3f50a79e-5582-4e5c-b1f4-9dcc1c82cece' + } + }, + { + adUnitCode: 'test-native2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1778', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: { + title: {}, + image: {}, + icon: {}, + sponsoredBy: {}, + body: {}, + cta: {} + } + }, + params: { + placementId: '2015defc-19db-4cf6-926d-d2d0d32122fa', + } + }, + { + adUnitCode: 'test-native3', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1779', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + native: {}, + }, + params: { + placementId: '8064026a-9932-45ae-b804-03491302ad88' + } + } + ]; + + 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://srv.freedomadnetwork.com/pb/req'); + + 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.originalBidRequest).to.equal(bidRequest); + + var data = JSON.parse(req.data); + expect(data.id).to.equal(bidRequest.bidId); + expect(data.placements[0]).to.equal(bidRequest.params.placementId); + } + }); + }); + + 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 main interpretResponse', function () { + const serverResponse = { + body: [{ + id: '8064026a1776', + bidid: '78e10bd4-aa67-40a6-b282-0f2697251eb3', + impid: '88faf7e7-bef8-43a5-9ef3-73db10c2af6b', + userId: '944c9c880be09af1e90da1f883538607', + cpm: 17.76, + currency: 'USD', + width: 300, + height: 250, + ttl: 60, + netRevenue: false, + crid: '03f3ed6f-1a9e-4276-8ad7-0dc5efae289e', + payload: '', + trackers: [], + mediaType: 'native', + domains: ['foo.com'], + }] + }; + + const bidResponses = spec.interpretResponse(serverResponse, { + originalBidRequest: { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '8064026a1776', + bidder: 'freedomadnetwork', + bidderRequestId: '15246a574e859f', + mediaTypes: { + banner: { sizes: [[300, 250]] } + }, + params: { + placementId: 'e6203f1e-bd6d-4f42-9895-d1a19cdb83c8' + } + } + }); + + expect(bidResponses).to.be.an('array').that.is.not.empty; + + const bid = serverResponse.body[0]; + const bidResponse = bidResponses[0]; + + expect(bidResponse.requestId).to.equal(bid.id); + expect(bidResponse.bidid).to.equal(bid.bidid); + expect(bidResponse.impid).to.equal(bid.impid); + expect(bidResponse.userId).to.equal(bid.userId); + expect(bidResponse.cpm).to.equal(bid.cpm); + expect(bidResponse.currency).to.equal(bid.currency); + expect(bidResponse.width).to.equal(bid.width); + expect(bidResponse.height).to.equal(bid.height); + expect(bidResponse.ad).to.equal(bid.payload); + expect(bidResponse.ttl).to.equal(bid.ttl); + expect(bidResponse.creativeId).to.equal(bid.crid); + expect(bidResponse.netRevenue).to.equal(bid.netRevenue); + expect(bidResponse.trackers).to.equal(bid.trackers); + expect(bidResponse.meta.mediaType).to.equal(bid.mediaType); + expect(bidResponse.meta.advertiserDomains).to.equal(bid.domains); + }); + + 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: [] }, {}); + + expect(bidResponses).to.be.an('array').that.is.empty; + }); + }); + + describe('Test getUserSyncs', function () { + it('getUserSyncs should return empty', function () { + const serverResponse = {}; + const syncOptions = {} + const userSyncPixels = spec.getUserSyncs(syncOptions, [serverResponse]) + expect(userSyncPixels).to.have.lengthOf(0); + }); + }); + + describe('Test onTimeout', function () { + it('onTimeout should not throw', function () { + expect(spec.onTimeout()).to.not.throw; + }); + }); + + describe('Test onBidWon', function () { + let sandbox, ajaxStub; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + ajaxStub = sandbox.stub(ajax, 'ajax'); + }); + + afterEach(function () { + sandbox.restore(); + ajaxStub.restore(); + }); + + const bid = { + bidid: '78e10bd4-aa67-40a6-b282-0f2697251eb3', + impid: '88faf7e7-bef8-43a5-9ef3-73db10c2af6b', + cpm: 17.76, + trackers: ['foo.com'], + } + + it('onBidWon empty bid should not throw', function () { + expect(spec.onBidWon({})).to.not.throw; + expect(ajaxStub.calledOnce).to.equal(true); + }); + + it('onBidWon valid bid should not throw', function () { + expect(spec.onBidWon(bid)).to.not.throw; + expect(ajaxStub.calledOnce).to.equal(true); + }); + }); + + describe('Test onSetTargeting', function () { + it('onSetTargeting should not throw', function () { + expect(spec.onSetTargeting()).to.not.throw; + }); + }); +}); diff --git a/test/spec/modules/fledgeForGpt_spec.js b/test/spec/modules/fledgeForGpt_spec.js deleted file mode 100644 index 8ab11171121..00000000000 --- a/test/spec/modules/fledgeForGpt_spec.js +++ /dev/null @@ -1,177 +0,0 @@ -import {onAuctionConfigFactory, setPAAPIConfigFactory, slotConfigurator} from 'modules/fledgeForGpt.js'; -import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; -import 'modules/appnexusBidAdapter.js'; -import 'modules/rubiconBidAdapter.js'; -import {deepSetValue} from '../../../src/utils.js'; -import {config} from 'src/config.js'; - -describe('fledgeForGpt module', () => { - let sandbox, fledgeAuctionConfig; - - beforeEach(() => { - sandbox = sinon.sandbox.create(); - fledgeAuctionConfig = { - seller: 'bidder', - mock: 'config' - }; - }); - afterEach(() => { - sandbox.restore(); - }); - - describe('slotConfigurator', () => { - let mockGptSlot, setGptConfig; - beforeEach(() => { - mockGptSlot = { - setConfig: sinon.stub(), - getAdUnitPath: () => 'mock/gpt/au' - }; - sandbox.stub(gptUtils, 'getGptSlotForAdUnitCode').callsFake(() => mockGptSlot); - setGptConfig = slotConfigurator(); - }); - it('should set GPT slot config', () => { - setGptConfig('au', [fledgeAuctionConfig]); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: fledgeAuctionConfig, - }] - }); - }); - - describe('when reset = true', () => { - it('should reset GPT slot config', () => { - setGptConfig('au', [fledgeAuctionConfig]); - mockGptSlot.setConfig.resetHistory(); - gptUtils.getGptSlotForAdUnitCode.resetHistory(); - setGptConfig('au', [], true); - sinon.assert.calledWith(gptUtils.getGptSlotForAdUnitCode, 'au'); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 'bidder', - auctionConfig: null - }] - }); - }); - - it('should reset only sellers with no fresh config', () => { - setGptConfig('au', [{seller: 's1'}, {seller: 's2'}]); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au', [{seller: 's1'}], true); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: {seller: 's1'} - }, { - configKey: 's2', - auctionConfig: null - }] - }) - }); - - it('should not reset sellers that were already reset', () => { - setGptConfig('au', [{seller: 's1'}]); - setGptConfig('au', [], true); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au', [], true); - sinon.assert.notCalled(mockGptSlot.setConfig); - }) - - it('should keep track of configuration history by slot', () => { - setGptConfig('au1', [{seller: 's1'}]); - setGptConfig('au1', [{seller: 's2'}], false); - setGptConfig('au2', [{seller: 's3'}]); - mockGptSlot.setConfig.resetHistory(); - setGptConfig('au1', [], true); - sinon.assert.calledWith(mockGptSlot.setConfig, { - componentAuction: [{ - configKey: 's1', - auctionConfig: null - }, { - configKey: 's2', - auctionConfig: null - }] - }); - }) - }); - }); - describe('onAuctionConfig', () => { - [ - 'fledgeForGpt', - 'paapi.gpt' - ].forEach(namespace => { - describe(`using ${namespace} config`, () => { - Object.entries({ - 'omitted': [undefined, true], - 'enabled': [true, true], - 'disabled': [false, false] - }).forEach(([t, [autoconfig, shouldSetConfig]]) => { - describe(`when autoconfig is ${t}`, () => { - beforeEach(() => { - const cfg = {}; - deepSetValue(cfg, `${namespace}.autoconfig`, autoconfig); - config.setConfig(cfg); - }); - afterEach(() => { - config.resetConfig(); - }); - - it(`should ${shouldSetConfig ? '' : 'NOT'} set GPT slot configuration`, () => { - const auctionConfig = {componentAuctions: [{seller: 'mock1'}, {seller: 'mock2'}]}; - const setGptConfig = sinon.stub(); - const markAsUsed = sinon.stub(); - onAuctionConfigFactory(setGptConfig)('aid', {au1: auctionConfig, au2: null}, markAsUsed); - if (shouldSetConfig) { - sinon.assert.calledWith(setGptConfig, 'au1', auctionConfig.componentAuctions); - sinon.assert.calledWith(setGptConfig, 'au2', []); - sinon.assert.calledWith(markAsUsed, 'au1'); - } else { - sinon.assert.notCalled(setGptConfig); - sinon.assert.notCalled(markAsUsed); - } - }); - }) - }) - }) - }) - }); - describe('setPAAPIConfigForGpt', () => { - let getPAAPIConfig, setGptConfig, setPAAPIConfigForGPT; - beforeEach(() => { - getPAAPIConfig = sinon.stub(); - setGptConfig = sinon.stub(); - setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig); - }); - - Object.entries({ - missing: null, - empty: {} - }).forEach(([t, configs]) => { - it(`does not set GPT slot config when config is ${t}`, () => { - getPAAPIConfig.returns(configs); - setPAAPIConfigForGPT('mock-filters'); - sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); - sinon.assert.notCalled(setGptConfig); - }) - }); - - it('sets GPT slot config for each ad unit that has PAAPI config, and resets the rest', () => { - const cfg = { - au1: { - componentAuctions: [{seller: 's1'}, {seller: 's2'}] - }, - au2: { - componentAuctions: [{seller: 's3'}] - }, - au3: null - } - getPAAPIConfig.returns(cfg); - setPAAPIConfigForGPT('mock-filters'); - sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); - Object.entries(cfg).forEach(([au, config]) => { - sinon.assert.calledWith(setGptConfig, au, config?.componentAuctions ?? [], true); - }) - }); - }) -}); diff --git a/test/spec/modules/flippBidAdapter_spec.js b/test/spec/modules/flippBidAdapter_spec.js index 518052ad91e..9602a156bed 100644 --- a/test/spec/modules/flippBidAdapter_spec.js +++ b/test/spec/modules/flippBidAdapter_spec.js @@ -99,6 +99,14 @@ describe('flippAdapter', function () { 'requestId': '237f4d1a293f99', 'cpm': 1.11, 'creative': 'Returned from server', + }, + 'contents': { + 'data': { + 'customData': { + 'compactHeight': 600, + 'standardHeight': 1800 + } + } } }] }, @@ -114,7 +122,7 @@ describe('flippAdapter', function () { cpm: 1.11, netRevenue: true, width: 300, - height: 600, + height: 1800, creativeId: 262838368, ttl: 30, ad: 'Returned from server', diff --git a/test/spec/modules/fluctBidAdapter_spec.js b/test/spec/modules/fluctBidAdapter_spec.js index ff6f8562a4e..32ca99ecd76 100644 --- a/test/spec/modules/fluctBidAdapter_spec.js +++ b/test/spec/modules/fluctBidAdapter_spec.js @@ -26,30 +26,30 @@ describe('fluctAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return true when dfpUnitCode is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { tagId: '10000:100000001', groupId: '1000000002', }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when groupId is not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { dfpUnitCode: '/1000/dfp_unit_code', tagId: '10000:100000001', }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/freewheel-sspBidAdapter_spec.js b/test/spec/modules/freewheel-sspBidAdapter_spec.js index 90ebe0b80ee..94b7f04b637 100644 --- a/test/spec/modules/freewheel-sspBidAdapter_spec.js +++ b/test/spec/modules/freewheel-sspBidAdapter_spec.js @@ -41,12 +41,12 @@ describe('freewheelSSP BidAdapter Test', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -73,12 +73,12 @@ describe('freewheelSSP BidAdapter Test', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/ftrackIdSystem_spec.js b/test/spec/modules/ftrackIdSystem_spec.js index ecd610a12fb..12e18ed5354 100644 --- a/test/spec/modules/ftrackIdSystem_spec.js +++ b/test/spec/modules/ftrackIdSystem_spec.js @@ -3,10 +3,10 @@ import * as utils from 'src/utils.js'; import { uspDataHandler } from 'src/adapterManager.js'; import { loadExternalScript } from 'src/adloader.js'; import { getGlobal } from 'src/prebidGlobal.js'; -import { init, setSubmoduleRegistry } from 'modules/userId/index.js'; +import {attachIdSystem, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {createEidsArray} from 'modules/userId/eids.js'; import {config} from 'src/config.js'; -let expect = require('chai').expect; +import 'src/prebid.js'; let server; @@ -380,10 +380,10 @@ describe('FTRACK ID System', () => { } }); - getGlobal().getUserIdsAsync().then(ids => { - expect(ids).to.deep.equal({ + return getGlobal().getUserIdsAsync().then(ids => { + expect(ids.ftrackId).to.deep.equal({ uid: 'device_test_id', - ftrackId: { + ext: { HHID: 'household_test_id', DeviceID: 'device_test_id', SingleDeviceID: 'single_device_test_id' @@ -558,5 +558,40 @@ describe('FTRACK ID System', () => { }); }); }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(ftrackIdSubmodule); + }); + it('should return the correct EID schema', () => { + // This is the schema returned from the ftrack decode() method + expect(createEidsArray({ + ftrackId: { + uid: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } + }, + foo: { + bar: 'baz' + }, + lorem: { + ipsum: '' + } + })).to.deep.equal([{ + source: 'flashtalking.com', + uids: [{ + atype: 1, + id: 'test-device-id', + ext: { + DeviceID: 'test-device-id', + SingleDeviceID: 'test-single-device-id', + HHID: 'test-household-id' + } + }] + }]); + }); }) }); diff --git a/test/spec/modules/gameraRtdProvider_spec.js b/test/spec/modules/gameraRtdProvider_spec.js new file mode 100644 index 00000000000..63029d85545 --- /dev/null +++ b/test/spec/modules/gameraRtdProvider_spec.js @@ -0,0 +1,223 @@ +import { submodule } from 'src/hook.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import * as utils from 'src/utils.js'; +import { subModuleObj } from 'modules/gameraRtdProvider.js'; + +describe('gameraRtdProvider', function () { + let logErrorSpy; + + beforeEach(function () { + logErrorSpy = sinon.spy(utils, 'logError'); + }); + + afterEach(function () { + logErrorSpy.restore(); + }); + + describe('subModuleObj', function () { + it('should have the correct module name', function () { + expect(subModuleObj.name).to.equal('gamera'); + }); + + it('successfully instantiates and returns true', function () { + expect(subModuleObj.init()).to.equal(true); + }); + }); + + describe('getBidRequestData', function () { + const reqBidsConfigObj = { + adUnits: [{ + code: 'test-div', + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, + ortb2Imp: { + ext: { + data: { + pbadslot: 'homepage-top-rect', + adUnitSpecificAttribute: '123', + } + } + }, + bids: [{ bidder: 'test' }] + }], + ortb2Fragments: { + global: { + site: { + name: 'example', + domain: 'page.example.com', + // OpenRTB 2.5 spec / Content Taxonomy + cat: ['IAB2'], + sectioncat: ['IAB2-2'], + pagecat: ['IAB2-2'], + + page: 'https://page.example.com/here.html', + ref: 'https://ref.example.com', + keywords: 'power tools, drills', + search: 'drill', + content: { + userrating: '4', + data: [{ + name: 'www.dataprovider1.com', // who resolved the segments + ext: { + segtax: 7, // taxonomy used to encode the segments + cids: ['iris_c73g5jq96mwso4d8'] + }, + // the bare minimum are the IDs. These IDs are the ones from the new IAB Content Taxonomy v3 + segment: [{ id: '687' }, { id: '123' }] + }] + }, + ext: { + data: { // fields that aren't part of openrtb 2.6 + pageType: 'article', + category: 'repair' + } + } + }, + // this is where the user data is placed + user: { + keywords: 'a,b', + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }] + }], + ext: { + data: { + registered: true, + interests: ['cars'] + } + } + } + } + } + }; + + let callback; + + beforeEach(function () { + callback = sinon.spy(); + window.gamera = undefined; + }); + + it('should queue command when gamera.getPrebidSegments is not available', function () { + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(window.gamera).to.exist; + expect(window.gamera.cmd).to.be.an('array'); + expect(window.gamera.cmd.length).to.equal(1); + expect(callback.called).to.be.false; + + // our callback should be executed if command queue is flushed + window.gamera.cmd.forEach(command => command()); + expect(callback.calledOnce).to.be.true; + }); + + it('should call enrichAuction directly when gamera.getPrebidSegments is available', function () { + window.gamera = { + getPrebidSegments: () => ({}) + }; + + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(callback.calledOnce).to.be.true; + }); + + it('should handle errors gracefully', function () { + window.gamera = { + getPrebidSegments: () => { + throw new Error('Test error'); + } + }; + + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(logErrorSpy.calledWith('gameraRtdProvider', 'Error getting segments:')).to.be.true; + expect(callback.calledOnce).to.be.true; + }); + + describe('segment enrichment', function () { + const mockSegments = { + user: { + data: [{ + name: 'gamera.ai', + ext: { + segtax: 4, + }, + segment: [{ id: 'user-1' }] + }] + }, + site: { + keywords: 'gamera,article,keywords', + content: { + data: [{ + name: 'gamera.ai', + ext: { + segtax: 7, + }, + segment: [{ id: 'site-1' }] + }] + } + }, + adUnits: { + 'test-div': { + key: 'value', + ext: { + data: { + gameraSegment: 'ad-1', + } + } + } + } + }; + + beforeEach(function () { + window.gamera = { + getPrebidSegments: () => mockSegments + }; + }); + + it('should enrich ortb2Fragments with user data', function () { + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(reqBidsConfigObj.ortb2Fragments.global.user.data).to.deep.include(mockSegments.user.data[0]); + + // check if existing attributes are not overwritten + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); + expect(reqBidsConfigObj.ortb2Fragments.global.user.data[0].segment[0].id).to.equal('1'); + expect(reqBidsConfigObj.ortb2Fragments.global.user.keywords).to.equal('a,b'); + expect(reqBidsConfigObj.ortb2Fragments.global.user.ext.data.registered).to.equal(true); + }); + + it('should enrich ortb2Fragments with site data', function () { + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data).to.deep.include(mockSegments.site.content.data[0]); + expect(reqBidsConfigObj.ortb2Fragments.global.site.keywords).to.equal('gamera,article,keywords'); + + // check if existing attributes are not overwritten + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].ext.segtax).to.equal(7); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.data[0].segment[0].id).to.equal('687'); + expect(reqBidsConfigObj.ortb2Fragments.global.site.ext.data.category).to.equal('repair'); + expect(reqBidsConfigObj.ortb2Fragments.global.site.content.userrating).to.equal('4'); + }); + + it('should enrich adUnits with segment data', function () { + subModuleObj.getBidRequestData(reqBidsConfigObj, callback); + + expect(reqBidsConfigObj.adUnits[0].ortb2Imp.key).to.equal('value'); + expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.gameraSegment).to.equal('ad-1'); + + // check if existing attributes are not overwritten + expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.adUnitSpecificAttribute).to.equal('123'); + expect(reqBidsConfigObj.adUnits[0].ortb2Imp.ext.data.pbadslot).to.equal('homepage-top-rect'); + }); + }); + }); +}); diff --git a/test/spec/modules/gammaBidAdapter_spec.js b/test/spec/modules/gammaBidAdapter_spec.js index f3a28c08576..2c83c3912e3 100644 --- a/test/spec/modules/gammaBidAdapter_spec.js +++ b/test/spec/modules/gammaBidAdapter_spec.js @@ -28,9 +28,9 @@ describe('gammaBidAdapter', function() { }); it('should return false when require params are not passed', () => { - let bid = Object.assign({}, bid); - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when params not passed correctly', () => { diff --git a/test/spec/modules/gamoshiBidAdapter_spec.js b/test/spec/modules/gamoshiBidAdapter_spec.js index 984830f67d4..8f9818ed901 100644 --- a/test/spec/modules/gamoshiBidAdapter_spec.js +++ b/test/spec/modules/gamoshiBidAdapter_spec.js @@ -379,7 +379,7 @@ describe('GamoshiAdapter', () => { const bidRequestWithVideo = utils.deepClone(bidRequest); bidRequestWithVideo.params.video = { - placement: 1, + plcmt: 1, minduration: 1, } @@ -398,7 +398,7 @@ describe('GamoshiAdapter', () => { 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.plcmt).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); @@ -408,7 +408,7 @@ describe('GamoshiAdapter', () => { playerSize: [302, 252], mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -431,7 +431,7 @@ describe('GamoshiAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, @@ -440,6 +440,7 @@ describe('GamoshiAdapter', () => { let response = spec.buildRequests([bidRequestWithVideo], bidRequest)[0]; expect(response.data.imp[0].video.ext.context).to.equal('instream'); bidRequestWithVideo.mediaTypes.video.context = 'outstream'; + bidRequestWithVideo.mediaTypes.video.context = 'outstream'; const bidRequestWithPosEquals1 = utils.deepClone(bidRequestWithVideo); bidRequestWithPosEquals1.mediaTypes.video.context = 'outstream'; @@ -460,7 +461,7 @@ describe('GamoshiAdapter', () => { context: 'instream', mimes: ['video/mpeg'], skip: 1, - placement: 1, + plcmt: 1, minduration: 1, playbackmethod: 1, startdelay: 1, diff --git a/test/spec/modules/genericAnalyticsAdapter_spec.js b/test/spec/modules/genericAnalyticsAdapter_spec.js index 2d9c7b4ae45..f574a33bf86 100644 --- a/test/spec/modules/genericAnalyticsAdapter_spec.js +++ b/test/spec/modules/genericAnalyticsAdapter_spec.js @@ -265,7 +265,7 @@ describe('Generic analytics', () => { handler([payload, {}]); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); @@ -275,7 +275,7 @@ describe('Generic analytics', () => { handler(payload); sinon.assert.calledWith(ajax, url, sinon.match.any, sinon.match(data => sinon.match(payload).test(parse(data))), - {method} + {method, keepalive: true} ); }); }); diff --git a/test/spec/modules/geoedgeRtdProvider_spec.js b/test/spec/modules/geoedgeRtdProvider_spec.js index ecc24bce6b5..5f3f2a5b1b8 100644 --- a/test/spec/modules/geoedgeRtdProvider_spec.js +++ b/test/spec/modules/geoedgeRtdProvider_spec.js @@ -143,6 +143,14 @@ describe('Geoedge RTD module', function () { const hasCurrency = dict['%_hbCurrency!'] === bid.currency; expect(hasCpm && hasCurrency); }); + it('return a dictionary of macros replaced with values from overrides object if provided', function () { + const bid = mockBid('testBidder'); + window.grumi.overrides = { site: 'test-overrides' }; + const overrides = window.grumi.overrides; + const dict = getMacros(bid, key); + const siteOveridden = dict['%%SITE%%'] === overrides.site; + expect(siteOveridden); + }); }); describe('onBidResponseEvent', function () { const bidFromA = mockBid('bidderA'); diff --git a/test/spec/modules/globalsunBidAdapter_spec.js b/test/spec/modules/globalsunBidAdapter_spec.js index 0d17c25363d..0651b05894f 100644 --- a/test/spec/modules/globalsunBidAdapter_spec.js +++ b/test/spec/modules/globalsunBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/globalsunBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'globalsun' +const bidder = 'globalsun'; describe('GlobalsunBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('GlobalsunBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('GlobalsunBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -112,6 +136,7 @@ describe('GlobalsunBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -129,7 +154,7 @@ describe('GlobalsunBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +170,56 @@ describe('GlobalsunBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +245,10 @@ describe('GlobalsunBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +263,38 @@ describe('GlobalsunBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +498,17 @@ describe('GlobalsunBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.globalsun.io/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.globalsun.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/gmosspBidAdapter_spec.js b/test/spec/modules/gmosspBidAdapter_spec.js index 8c3aa6c94cb..77644b136db 100644 --- a/test/spec/modules/gmosspBidAdapter_spec.js +++ b/test/spec/modules/gmosspBidAdapter_spec.js @@ -27,10 +27,10 @@ describe('GmosspAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gnetBidAdapter_spec.js b/test/spec/modules/gnetBidAdapter_spec.js index f1af3b71103..8e2cfadc96b 100644 --- a/test/spec/modules/gnetBidAdapter_spec.js +++ b/test/spec/modules/gnetBidAdapter_spec.js @@ -32,10 +32,10 @@ describe('gnetAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/goldbachBidAdapter_spec.js b/test/spec/modules/goldbachBidAdapter_spec.js index 93956d2caf9..6ea84ed6931 100644 --- a/test/spec/modules/goldbachBidAdapter_spec.js +++ b/test/spec/modules/goldbachBidAdapter_spec.js @@ -36,23 +36,23 @@ describe('GoldbachXandrAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/gptPreAuction_spec.js b/test/spec/modules/gptPreAuction_spec.js index fa2236f77c6..989a5f376bb 100644 --- a/test/spec/modules/gptPreAuction_spec.js +++ b/test/spec/modules/gptPreAuction_spec.js @@ -2,10 +2,16 @@ import { appendGptSlots, appendPbAdSlot, _currentConfig, - makeBidRequestsHook + makeBidRequestsHook, + getAuctionsIdsFromTargeting, + getSegments, + getSignals, + getSignalsArrayByAuctionsIds, + getSignalsIntersection } from 'modules/gptPreAuction.js'; import { config } from 'src/config.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import { taxonomies } from '../../../libraries/gptUtils/gptUtils.js'; describe('GPT pre-auction module', () => { let sandbox; @@ -25,6 +31,87 @@ describe('GPT pre-auction module', () => { makeSlot({ code: 'slotCode4', divId: 'div5' }) ]; + const mockTargeting = {'/123456/header-bid-tag-0': {'hb_deal_rubicon': '1234', 'hb_deal': '1234', 'hb_pb': '0.53', 'hb_adid': '148018fe5e', 'hb_bidder': 'rubicon', 'foobar': '300x250', 'hb_pb_rubicon': '0.53', 'hb_adid_rubicon': '148018fe5e', 'hb_bidder_rubicon': 'rubicon', 'hb_deal_appnexus': '4321', 'hb_pb_appnexus': '0.1', 'hb_adid_appnexus': '567891011', 'hb_bidder_appnexus': 'appnexus'}} + + const mockAuctionManager = { + findBidByAdId(adId) { + const bidsMap = { + '148018fe5e': { + auctionId: mocksAuctions[0].auctionId + }, + '567891011': { + auctionId: mocksAuctions[1].auctionId + }, + }; + return bidsMap[adId]; + }, + index: { + getAuction({ auctionId }) { + return mocksAuctions.find(auction => auction.auctionId === auctionId); + } + } + } + + const mocksAuctions = [ + { + auctionId: '1111', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '1' + }, { + id: '2' + }] + }], + } + } + }) + }, + { + auctionId: '234234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }] + }] + } + } + }), + }, { + auctionId: '234324234', + getFPD: () => ({ + global: { + user: { + data: [{ + name: 'dataprovider.com', + ext: { + segtax: 4 + }, + segment: [{ + id: '2' + }, { + id: '3' + }] + }] + } + } + }) + }, + ] + describe('appendPbAdSlot', () => { // sets up our document body to test the pbAdSlot dom actions against document.body.innerHTML = '
test1
' + @@ -122,6 +209,17 @@ describe('GPT pre-auction module', () => { expect(adUnit.ortb2Imp.ext.data.adserver).to.deep.equal({ name: 'gam', adslot: '/12345/slotCode2' }); }); + it('returns full ad unit path even if mcmEnabled is true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + expect(appendGptSlots([adUnit])).to.eql({ + '/12345,21212/slot': '/12345,21212/slot' + }) + }) + it('will not trim child id if mcmEnabled is not set to true', () => { window.googletag.pubads().setSlots([ makeSlot({ code: '/12345,21212/slotCode1', divId: 'div1' }), @@ -188,7 +286,7 @@ describe('GPT pre-auction module', () => { customGptSlotMatching: false, customPbAdSlot: false, customPreAuction: false, - useDefaultPreAuction: false + useDefaultPreAuction: true }); }); }); @@ -372,6 +470,33 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + it('should pass full slot path to customPreAuction when mcmEnabled is true', () => { + const customPreAuction = sinon.stub(); + config.setConfig({ + gptPreAuction: { + enabled: true, + mcmEnabled: true, + customPreAuction + } + }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + sinon.assert.calledWith(customPreAuction, adUnit, '/12345/slot', adUnit.code); + }); + + it('should not choke if gpt is not available', () => { + config.setConfig({ + gptPreAuction: { + enabled: true + } + }); + sandbox.stub(window, 'googletag').value(null); + makeBidRequestsHook(sinon.stub(), [{}]); + }) + it('should use useDefaultPreAuction logic', () => { config.setConfig({ gptPreAuction: { @@ -453,5 +578,70 @@ describe('GPT pre-auction module', () => { runMakeBidRequests(testAdUnits); expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); + + it('sets gpid when mcmEnabled: true', () => { + config.setConfig({ gptPreAuction: { enabled: true, mcmEnabled: true } }); + window.googletag.pubads().setSlots([ + makeSlot({ code: '/12345,21212/slot', divId: 'div1' }), + ]); + const adUnit = {code: '/12345,21212/slot'}; + makeBidRequestsHook(sinon.stub(), [adUnit]); + expect(adUnit.ortb2Imp.ext.gpid).to.eql('/12345/slot'); + }); + }); + + describe('pps gpt config', () => { + it('should parse segments from fpd', () => { + const twoSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 4); + expect(JSON.stringify(twoSegments)).to.equal(JSON.stringify(['1', '2'])); + const zeroSegments = getSegments(mocksAuctions[0].getFPD().global, ['user.data'], 6); + expect(zeroSegments).to.length(0); + }); + + it('should return signals from fpd', () => { + const signals = getSignals(mocksAuctions[0].getFPD().global); + const expectedSignals = [{ taxonomy: taxonomies[0], values: ['1', '2'] }]; + expect(signals).to.eql(expectedSignals); + }); + + it('should properly get auctions ids from targeting', () => { + const auctionsIds = getAuctionsIdsFromTargeting(mockTargeting, mockAuctionManager); + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]) + }); + + it('should filter out adIds that do not map to any auction', () => { + const auctionsIds = getAuctionsIdsFromTargeting({ + ...mockTargeting, + 'au': {'hb_adid': 'missing'}, + }, mockAuctionManager); + expect(auctionsIds).to.eql([mocksAuctions[0].auctionId, mocksAuctions[1].auctionId]); + }) + + it('should properly return empty array of auction ids for invalid targeting', () => { + let auctionsIds = getAuctionsIdsFromTargeting({}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + auctionsIds = getAuctionsIdsFromTargeting({'/123456/header-bid-tag-0/bg': {'invalidContent': '123'}}, mockAuctionManager); + expect(Array.isArray(auctionsIds)).to.equal(true); + expect(auctionsIds).to.length(0); + }); + + it('should properly get signals from auctions', () => { + const signals = getSignalsArrayByAuctionsIds(['1111', '234234', '234324234'], mockAuctionManager.index); + const intersection = getSignalsIntersection(signals); + const expectedResult = { IAB_AUDIENCE_1_1: { values: ['2'] }, IAB_CONTENT_2_2: { values: [] } }; + expect(JSON.stringify(intersection)).to.be.equal(JSON.stringify(expectedResult)); + }); + + it('should return empty signals array for empty auctions ids array', () => { + const signals = getSignalsArrayByAuctionsIds([], mockAuctionManager.index); + expect(Array.isArray(signals)).to.equal(true); + expect(signals).to.length(0); + }); + + it('should return properly formatted object for getSignalsIntersection invoked with empty array', () => { + const signals = getSignalsIntersection([]); + expect(Object.keys(signals)).to.contain.members(taxonomies); + }); }); }); diff --git a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js index 918da50d8bc..7eeaa7a6c67 100644 --- a/test/spec/modules/greenbidsAnalyticsAdapter_spec.js +++ b/test/spec/modules/greenbidsAnalyticsAdapter_spec.js @@ -7,7 +7,7 @@ import { generateUUID } from '../../../src/utils.js'; import * as utils from 'src/utils.js'; -import {expect} from 'chai'; +import { expect } from 'chai'; import sinon from 'sinon'; const events = require('src/events'); @@ -65,17 +65,17 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { greenbidsAnalyticsAdapter.disableAnalytics(); }); - describe('#getCachedAuction()', function() { - const existing = {timeoutBids: [{}]}; + describe('#getCachedAuction()', function () { + const existing = { timeoutBids: [{}] }; greenbidsAnalyticsAdapter.cachedAuctions['test_auction_id'] = existing; - it('should get the existing cached object if it exists', function() { + it('should get the existing cached object if it exists', function () { const result = greenbidsAnalyticsAdapter.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() { + it('should create a new object and store it in the cache on cache miss', function () { const result = greenbidsAnalyticsAdapter.getCachedAuction('no_such_id'); expect(result).to.deep.include({ @@ -84,7 +84,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('when formatting JSON payload sent to backend', function() { + describe('when formatting JSON payload sent to backend', function () { const receivedBids = [ { auctionId: auctionId, @@ -106,7 +106,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { timeToRespond: 100, cpm: 0.08, currency: 'USD', - ad: 'fake ad2' + ad: 'fake ad2', + params: {'placement ID': 12784} }, { auctionId: auctionId, @@ -149,16 +150,16 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); } - describe('#createCommonMessage', function() { - it('should correctly serialize some common fields', function() { + describe('#createCommonMessage', function () { + it('should correctly serialize some common fields', function () { const message = greenbidsAnalyticsAdapter.createCommonMessage(auctionId); assertHavingRequiredMessageFields(message); }); }); - describe('#serializeBidResponse', function() { - it('should handle BID properly with timeout false and hasBid true', function() { + describe('#serializeBidResponse', function () { + it('should handle BID properly with timeout false and hasBid true', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(receivedBids[0], BIDDER_STATUS.BID); expect(result).to.include({ @@ -168,7 +169,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - it('should handle NO_BID properly and set hasBid to false', function() { + it('should handle NO_BID properly and set hasBid to false', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.NO_BID); expect(result).to.include({ @@ -178,7 +179,7 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - it('should handle TIMEOUT properly and set isTimeout to true', function() { + it('should handle TIMEOUT properly and set isTimeout to true', function () { const result = greenbidsAnalyticsAdapter.serializeBidResponse(noBids[0], BIDDER_STATUS.TIMEOUT); expect(result).to.include({ @@ -189,8 +190,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('#addBidResponseToMessage()', function() { - it('should add a bid response in the output message, grouped by adunit_id and bidder', function() { + describe('#addBidResponseToMessage()', function () { + it('should add a bid response in the output message, grouped by adunit_id and bidder', function () { const message = { adUnits: [ { @@ -208,14 +209,15 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { bidder: 'greenbids', isTimeout: false, hasBid: false, + params: {} } ] }); }); }); - describe('#createBidMessage()', function() { - it('should format auction message sent to the backend', function() { + describe('#createBidMessage()', function () { + it('should format auction message sent to the backend', function () { const args = { auctionId: auctionId, timestamp: 1234567890, @@ -258,10 +260,9 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { noBids: noBids }; - sinon.stub(greenbidsAnalyticsAdapter, 'getCachedAuction').returns({timeoutBids: timeoutBids}); + sinon.stub(greenbidsAnalyticsAdapter, 'getCachedAuction').returns({ timeoutBids: timeoutBids }); const result = greenbidsAnalyticsAdapter.createBidMessage(args, timeoutBids); greenbidsAnalyticsAdapter.getCachedAuction.restore(); - assertHavingRequiredMessageFields(result); expect(result).to.deep.include({ auctionElapsed: 100, @@ -278,12 +279,18 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { { bidder: 'greenbids', isTimeout: false, - hasBid: true + hasBid: true, + params: {}, + cpm: 0.1, + currency: 'USD', }, { bidder: 'greenbidsx', isTimeout: false, - hasBid: true + hasBid: true, + params: { 'placement ID': 12784 }, + cpm: 0.08, + currency: 'USD', } ] }, @@ -312,7 +319,10 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { { bidder: 'greenbids', isTimeout: true, - hasBid: true + hasBid: true, + cpm: 0.09, + currency: 'USD', + params: {} } ] } @@ -321,8 +331,8 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('#handleBidTimeout()', function() { - it('should cached the timeout bid as BID_TIMEOUT event was triggered', function() { + describe('#handleBidTimeout()', function () { + it('should cached the timeout bid as BID_TIMEOUT event was triggered', function () { greenbidsAnalyticsAdapter.cachedAuctions['test_timeout_auction_id'] = { 'timeoutBids': [] }; const args = [{ auctionId: 'test_timeout_auction_id', @@ -369,28 +379,28 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { events.getEvents.restore(); }); - it('should call handleAuctionInit as AUCTION_INIT trigger event', function() { + it('should call handleAuctionInit as AUCTION_INIT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionInit'); - events.emit(constants.EVENTS.AUCTION_INIT, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.AUCTION_INIT, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionInit, 1); greenbidsAnalyticsAdapter.handleAuctionInit.restore(); }); - it('should call handleBidTimeout as BID_TIMEOUT trigger event', function() { + it('should call handleBidTimeout as BID_TIMEOUT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleBidTimeout'); - events.emit(constants.EVENTS.BID_TIMEOUT, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.BID_TIMEOUT, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleBidTimeout, 1); greenbidsAnalyticsAdapter.handleBidTimeout.restore(); }); - it('should call handleAuctionEnd as AUCTION_END trigger event', function() { + it('should call handleAuctionEnd as AUCTION_END trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleAuctionEnd'); - events.emit(constants.EVENTS.AUCTION_END, {auctionId: 'auctionId'}); + events.emit(constants.EVENTS.AUCTION_END, { auctionId: 'auctionId' }); sinon.assert.callCount(greenbidsAnalyticsAdapter.handleAuctionEnd, 1); greenbidsAnalyticsAdapter.handleAuctionEnd.restore(); }); - it('should call handleBillable as BILLABLE_EVENT trigger event', function() { + it('should call handleBillable as BILLABLE_EVENT trigger event', function () { sinon.spy(greenbidsAnalyticsAdapter, 'handleBillable'); events.emit(constants.EVENTS.BILLABLE_EVENT, { type: 'auction', @@ -403,34 +413,34 @@ describe('Greenbids Prebid AnalyticsAdapter Testing', function () { }); }); - describe('isSampled', function() { - it('should return true for invalid sampling rates', function() { + describe('isSampled', function () { + it('should return true for invalid sampling rates', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', -1, 0.0)).to.be.true; expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 1.2, 0.0)).to.be.true; }); - it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function() { + it('should return determinist falsevalue for valid sampling rate given the predifined id and rate', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.false; }); - it('should return determinist true value for valid sampling rate given the predifined id and rate', function() { + it('should return determinist true value for valid sampling rate given the predifined id and rate', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0)).to.be.true; }); - it('should return determinist true value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + it('should return determinist true value for valid sampling rate given the predifined id and rate when we split to non exploration first', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.9999, 0.0, 1.0)).to.be.true; }); - it('should return determinist false value for valid sampling rate given the predifined id and rate when we split to non exploration first', function() { + it('should return determinist false value for valid sampling rate given the predifined id and rate when we split to non exploration first', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0, 1.0)).to.be.false; }); }); - describe('isSampled when analytic isforced', function() { + describe('isSampled when analytic isforced', function () { before(() => { sinon.stub(utils, 'getParameterByName').callsFake(par => par === 'greenbids_force_sampling' ? true : undefined); }); - it('should return determinist true when sampling flag activated', function() { + it('should return determinist true when sampling flag activated', function () { expect(isSampled('ce1f3692-632c-4cfd-9e40-0c2ad625ec56', 0.0001, 0.0)).to.be.true; }); after(() => { diff --git a/test/spec/modules/gridBidAdapter_spec.js b/test/spec/modules/gridBidAdapter_spec.js index efd7b06685f..18f1f7aa7c8 100644 --- a/test/spec/modules/gridBidAdapter_spec.js +++ b/test/spec/modules/gridBidAdapter_spec.js @@ -30,12 +30,12 @@ describe('TheMediaGrid Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -950,6 +950,33 @@ describe('TheMediaGrid Adapter', function () { expect(payload.tmax).to.equal(null); }) + it('should add ORTB2 device data to the request', function () { + const bidderRequestWithDevice = { + ...bidderRequest, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }, + }; + + const [request] = spec.buildRequests([bidRequests[0]], bidderRequestWithDevice); + const payload = parseRequest(request.data); + + expect(payload.device).to.deep.equal(bidderRequestWithDevice.ortb2.device); + }); + describe('floorModule', function () { const floorTestData = { 'currency': 'USD', diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 1c5940c06a3..8e7a80281ab 100644 --- a/test/spec/modules/gumgumBidAdapter_spec.js +++ b/test/spec/modules/gumgumBidAdapter_spec.js @@ -44,9 +44,9 @@ describe('gumgumAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789' }; @@ -54,33 +54,33 @@ describe('gumgumAdapter', function () { }); it('should return true when inslot sends sizes and trackingid', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789', 'sizes': [[0, 1], [2, 3], [4, 5], [6, 7]] }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when no unit type is specified', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when bidfloor is not a number', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'inSlot': '789', 'bidfloor': '0.50' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false if invalid request id is found', function () { @@ -100,6 +100,28 @@ describe('gumgumAdapter', function () { describe('buildRequests', function () { let sizesArray = [[300, 250], [300, 600]]; + const bidderRequest = { + ortb2: { + site: { + content: { + data: [{ + name: 'www.iris.com', + ext: { + segtax: 500, + cids: ['iris_c73g5jq96mwso4d8'] + } + }] + }, + page: 'http://pub.com/news', + ref: 'http://google.com', + publisher: { + id: 'p10000', + domain: 'pub.com' + } + } + } + }; + let bidRequests = [ { gppString: 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN', @@ -259,19 +281,17 @@ describe('gumgumAdapter', function () { const bidRequest = spec.buildRequests([request])[0]; expect(bidRequest.data).to.have.property('iriscat'); }); + it('should set the irisid param when found iris_c73g5jq96mwso4d8', function() { + const request = { ...bidRequests[0], params: { irisid: 'abc123' } }; + const bidRequest = spec.buildRequests([request], bidderRequest)[0]; + expect(bidRequest.data).to.have.property('irisid', 'iris_c73g5jq96mwso4d8'); + }); 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]; @@ -285,10 +305,38 @@ describe('gumgumAdapter', function () { }); it('should set the global placement id (gpid) if in adserver property', function () { - const req = { ...bidRequests[0], ortb2Imp: { ext: { data: { adserver: { name: 'test', adslot: 123456 } } } } } + const req = { ...bidRequests[0], + ortb2Imp: { + ext: { + gpid: '/17037559/jeusol/jeusol_D_1', + 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); + expect(bidRequest.data.gpid).to.equal('/17037559/jeusol/jeusol_D_1'); + }); + it('should set ae value to 1 for PAAPI', function () { + const req = { ...bidRequests[0], + ortb2Imp: { + ext: { + ae: 1, + data: { + adserver: { + name: 'test', + adslot: 123456 + } + } + } + } } + const bidRequest = spec.buildRequests([req])[0]; + expect(bidRequest.data).to.have.property('ae'); + expect(bidRequest.data.ae).to.equal(true); }); it('should set the global placement id (gpid) if in pbadslot property', function () { @@ -737,6 +785,40 @@ describe('gumgumAdapter', function () { expect(bidRequest.data.pu.includes('ggad')).to.be.false; expect(bidRequest.data.pu.includes('ggdeal')).to.be.false; }); + + it('should handle ORTB2 device data', function () { + const ortb2 = { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + ip: '127.0.0.1', + ipv6: '51dc:5e20:fd6a:c955:66be:03b4:dfa3:35b2', + }, + }; + + const bidRequest = spec.buildRequests(bidRequests, { ortb2 })[0]; + + expect(bidRequest.data.dnt).to.equal(ortb2.device.dnt); + expect(bidRequest.data.ua).to.equal(ortb2.device.ua); + expect(bidRequest.data.lang).to.equal(ortb2.device.language); + expect(bidRequest.data.dt).to.equal(ortb2.device.devicetype); + expect(bidRequest.data.make).to.equal(ortb2.device.make); + expect(bidRequest.data.model).to.equal(ortb2.device.model); + expect(bidRequest.data.os).to.equal(ortb2.device.os); + expect(bidRequest.data.osv).to.equal(ortb2.device.osv); + expect(bidRequest.data.foddid).to.equal(ortb2.device.ext.fiftyonedegrees_deviceId); + expect(bidRequest.data.ip).to.equal(ortb2.device.ip); + expect(bidRequest.data.ipv6).to.equal(ortb2.device.ipv6); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index 85c8cc11c9e..70aaf06bcc8 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -1,12 +1,15 @@ -import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import * as utils from 'src/utils.js'; +import {hadronIdSubmodule, storage, LS_TAM_KEY} from 'modules/hadronIdSystem.js'; +import {server} from 'test/mocks/xhr.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('HadronIdSystem', function () { - describe('getId', function() { + const HADRON_TEST = 'tstCachedHadronId1'; + describe('getId', function () { let getDataFromLocalStorageStub; - beforeEach(function() { + beforeEach(function () { getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); }); @@ -14,42 +17,47 @@ describe('HadronIdSystem', function () { getDataFromLocalStorageStub.restore(); }); - it('gets a hadronId', function() { + it('gets a cached hadronid', function () { const config = { params: {} }; - const callbackSpy = sinon.spy(); - const callback = hadronIdSubmodule.getId(config).callback; - callback(callbackSpy); - const request = server.requests[0]; - expect(request.url).to.match(/^https:\/\/id\.hadron\.ad\.gt\/api\/v1\/pbhid/); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); - }); - - it('gets a cached hadronid', function() { - const config = { - params: {} - }; - getDataFromLocalStorageStub.withArgs('auHadronId').returns('tstCachedHadronId1'); - + getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(HADRON_TEST); const result = hadronIdSubmodule.getId(config); - expect(result).to.deep.equal({ id: { hadronId: 'tstCachedHadronId1' } }); + expect(result).to.deep.equal({id: HADRON_TEST}); }); - it('allows configurable id url', function() { + it('allows configurable id url', function () { const config = { params: { url: 'https://hadronid.publync.com' } }; + getDataFromLocalStorageStub.withArgs(LS_TAM_KEY).returns(null); const callbackSpy = sinon.spy(); const callback = hadronIdSubmodule.getId(config).callback; callback(callbackSpy); const request = server.requests[0]; expect(request.url).to.match(/^https:\/\/hadronid\.publync\.com\//); - request.respond(200, { 'Content-Type': 'application/json' }, JSON.stringify({ hadronId: 'testHadronId1' })); - expect(callbackSpy.lastCall.lastArg).to.deep.equal({ id: { hadronId: 'testHadronId1' } }); }); }); + + describe('eids', () => { + before(() => { + attachIdSystem(hadronIdSubmodule); + }); + it('hadronId', function () { + const userId = { + hadronId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'audigent.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/hadronRtdProvider_spec.js b/test/spec/modules/hadronRtdProvider_spec.js index b9e07c97f84..46877f246b5 100644 --- a/test/spec/modules/hadronRtdProvider_spec.js +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -1,15 +1,20 @@ -// TODO: this and hadronRtdProvider_spec are a copy-paste of each other - import {config} from 'src/config.js'; -import {HALOID_LOCAL_NAME, RTD_LOCAL_NAME, addRealTimeData, getRealTimeData, hadronSubmodule, storage} from 'modules/hadronRtdProvider.js'; +import { + HADRONID_LOCAL_NAME, + RTD_LOCAL_NAME, + addRealTimeData, + getRealTimeData, + hadronSubmodule, + storage +} from 'modules/hadronRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; -describe('hadronRtdProvider', function() { +describe('hadronRtdProvider', function () { let getDataFromLocalStorageStub; - beforeEach(function() { + beforeEach(function () { config.resetConfig(); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); }); @@ -18,19 +23,19 @@ describe('hadronRtdProvider', function() { getDataFromLocalStorageStub.restore(); }); - describe('hadronSubmodule', function() { + describe('hadronSubmodule', function () { it('successfully instantiates', function () { - expect(hadronSubmodule.init()).to.equal(true); + expect(hadronSubmodule.init()).to.equal(true); }); }); - describe('Add Real-Time Data', function() { - it('merges ortb2 data', function() { + describe('Add Real-Time Data', function () { + it('merges ortb2 data', function () { let rtdConfig = {}; const setConfigUserObj1 = { name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -38,7 +43,7 @@ describe('hadronRtdProvider', function() { const setConfigUserObj2 = { name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1914' }] @@ -125,12 +130,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.deep.include.members([setConfigSiteObj1, rtdSiteObj1]); }); - it('merges ortb2 data without duplication', function() { + it('merges ortb2 data without duplication', function () { let rtdConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -138,7 +143,7 @@ describe('hadronRtdProvider', function() { const userObj2 = { name: 'www.dataprovider2.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1914' }] @@ -197,12 +202,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.have.lengthOf(1); }); - it('merges bidder-specific ortb2 data', function() { + it('merges bidder-specific ortb2 data', function () { let rtdConfig = {}; const configUserObj1 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1776' }] @@ -210,7 +215,7 @@ describe('hadronRtdProvider', function() { const configUserObj2 = { name: 'www.dataprovider2.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1914' }] @@ -218,7 +223,7 @@ describe('hadronRtdProvider', function() { const configUserObj3 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '2003' }] @@ -374,12 +379,12 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.deep.include.members([configSiteObj2, rtdSiteObj2]); }); - it('merges bidder-specific ortb2 data without duplication', function() { + it('merges bidder-specific ortb2 data without duplication', function () { let rtdConfig = {}; const userObj1 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1776' }] @@ -387,7 +392,7 @@ describe('hadronRtdProvider', function() { const userObj2 = { name: 'www.dataprovider2.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '1914' }] @@ -395,7 +400,7 @@ describe('hadronRtdProvider', function() { const userObj3 = { name: 'www.dataprovider1.com', - ext: { segtax: 3 }, + ext: {segtax: 3}, segment: [{ id: '2003' }] @@ -503,10 +508,10 @@ describe('hadronRtdProvider', function() { expect(ortb2Config.site.content.data).to.have.lengthOf(2); }); - it('allows publisher defined rtd ortb2 logic', function() { + it('allows publisher defined rtd ortb2 logic', function () { const rtdConfig = { params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) { if (rtd.ortb2.user.data[0].segment[0].id == '1776') { pbConfig.setConfig({ortb2: rtd.ortb2}); } else { @@ -520,7 +525,7 @@ describe('hadronRtdProvider', function() { const rtdUserObj1 = { name: 'www.dataprovider.com', - ext: { taxonomyname: 'iab_audience_taxonomy' }, + ext: {taxonomyname: 'iab_audience_taxonomy'}, segment: [{ id: '1776' }] @@ -566,10 +571,10 @@ describe('hadronRtdProvider', function() { expect(config.getConfig().ortb2).to.deep.equal({}); }); - it('allows publisher defined adunit logic', function() { + it('allows publisher defined adunit logic', function () { const rtdConfig = { params: { - handleRtd: function(bidConfig, rtd, rtdConfig, pbConfig) { + handleRtd: function (bidConfig, rtd, rtdConfig, pbConfig) { var adUnits = bidConfig.adUnits; for (var i = 0; i < adUnits.length; i++) { var adUnit = adUnits[i]; @@ -631,8 +636,8 @@ describe('hadronRtdProvider', function() { }); }); - describe('Get Real-Time Data', function() { - it('gets rtd from local storage cache', function() { + describe('Get Real-Time Data', function () { + it('gets rtd from local storage cache', function () { const rtdConfig = { params: { segmentCache: true @@ -667,12 +672,12 @@ describe('hadronRtdProvider', function() { }; getDataFromLocalStorageStub.withArgs(RTD_LOCAL_NAME).returns(JSON.stringify(cachedRtd)); - - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + getRealTimeData(bidConfig, () => { + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + }, rtdConfig, {}); }); - it('gets real-time data via async request', function() { + it('gets real-time data via async request', function () { const setConfigSiteObj1 = { name: 'www.audigent.com', ext: { @@ -737,17 +742,15 @@ describe('hadronRtdProvider', function() { } }; - getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHadronId1'); - 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('hadronId', 'testHadronId1'); - - request.respond(200, responseHeader, JSON.stringify(data)); - - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + getDataFromLocalStorageStub.withArgs(HADRONID_LOCAL_NAME).returns('testHadronId1'); + getRealTimeData(bidConfig, () => { + 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('hadronId', 'testHadronId1'); + request.respond(200, responseHeader, JSON.stringify(data)); + expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); + }, rtdConfig, {}); }); }); }); diff --git a/test/spec/modules/humansecurityRtdProvider_spec.js b/test/spec/modules/humansecurityRtdProvider_spec.js new file mode 100644 index 00000000000..627c3a6c1e4 --- /dev/null +++ b/test/spec/modules/humansecurityRtdProvider_spec.js @@ -0,0 +1,210 @@ +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +import * as utils from '../../../src/utils.js'; +import * as hook from '../../../src/hook.js'; +import * as refererDetection from '../../../src/refererDetection.js'; + +import { __TEST__ } from '../../../modules/humansecurityRtdProvider.js'; + +const { + SUBMODULE_NAME, + SCRIPT_URL, + main, + load, + onImplLoaded, + onImplMessage, + onGetBidRequestData +} = __TEST__; + +describe('humansecurity RTD module', function () { + let sandbox; + + const stubUuid = 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee'; + const sonarStubId = `sonar_${stubUuid}`; + const stubWindow = { [sonarStubId]: undefined }; + + beforeEach(function() { + sandbox = sinon.sandbox.create(); + sandbox.stub(utils, 'getWindowSelf').returns(stubWindow); + sandbox.stub(utils, 'generateUUID').returns(stubUuid); + sandbox.stub(refererDetection, 'getRefererInfo').returns({ domain: 'example.com' }); + }); + afterEach(function() { + sandbox.restore(); + }); + + describe('Initialization step', function () { + let sandbox2; + let connectSpy; + beforeEach(function() { + sandbox2 = sinon.sandbox.create(); + connectSpy = sandbox.spy(); + // Once the impl script is loaded, it registers the API using session ID + sandbox2.stub(stubWindow, sonarStubId).value({ connect: connectSpy }); + }); + afterEach(function () { + sandbox2.restore(); + }); + + it('should accept valid configurations', function () { + // Default configuration - empty + expect(() => load({})).to.not.throw(); + expect(() => load({ params: {} })).to.not.throw(); + // Configuration with clientId + expect(() => load({ params: { clientId: 'customer123' } })).to.not.throw(); + }); + + it('should throw an Error on invalid configuration', function () { + expect(() => load({ params: { clientId: 123 } })).to.throw(); + expect(() => load({ params: { clientId: 'abc.def' } })).to.throw(); + expect(() => load({ params: { clientId: '1' } })).to.throw(); + expect(() => load({ params: { clientId: '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ' } })).to.throw(); + }); + + it('should insert implementation script', () => { + load({ }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + + const args = loadExternalScriptStub.getCall(0).args; + expect(args[0]).to.be.equal(`${SCRIPT_URL}?r=example.com`); + expect(args[2]).to.be.equal(SUBMODULE_NAME); + expect(args[3]).to.be.equal(onImplLoaded); + expect(args[4]).to.be.equal(null); + expect(args[5]).to.be.deep.equal({ 'data-sid': 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee' }); + }); + + it('should insert external script element with "customerId" info from config', () => { + load({ params: { clientId: 'customer123' } }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + + const args = loadExternalScriptStub.getCall(0).args; + expect(args[0]).to.be.equal(`${SCRIPT_URL}?r=example.com&c=customer123`); + }); + + it('should connect to the implementation script once it loads', function () { + load({ }); + + expect(loadExternalScriptStub.calledOnce).to.be.true; + expect(connectSpy.calledOnce).to.be.true; + + const args = connectSpy.getCall(0).args; + expect(args[0]).to.haveOwnProperty('cmd'); // pbjs global + expect(args[0]).to.haveOwnProperty('que'); + expect(args[1]).to.be.equal(onImplMessage); + }); + }); + + describe('Bid enrichment step', function () { + const hmnsData = { 'v1': 'sometoken' }; + + let sandbox2; + let callbackSpy; + let reqBidsConfig; + beforeEach(function() { + sandbox2 = sinon.sandbox.create(); + callbackSpy = sandbox2.spy(); + reqBidsConfig = { ortb2Fragments: { bidder: {}, global: {} } }; + }); + afterEach(function () { + sandbox2.restore(); + }); + + it('should add empty device.ext.hmns to global ortb2 when data is yet to be received from the impl script', () => { + load({ }); + + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); + expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals({}); + }); + + it('should add the default device.ext.hmns to global ortb2 when no "hmns" data was yet received', () => { + load({ }); + + onImplMessage({ type: 'info', data: 'not a hmns message' }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); + expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals({}); + }); + + it('should add device.ext.hmns with received tokens to global ortb2 when the data was received', () => { + load({ }); + + onImplMessage({ type: 'hmns', data: hmnsData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); + expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals(hmnsData); + }); + + it('should update device.ext.hmns with new data', () => { + load({ }); + + onImplMessage({ type: 'hmns', data: { 'v1': 'should be overwritten' } }); + onImplMessage({ type: 'hmns', data: hmnsData }); + onGetBidRequestData(reqBidsConfig, callbackSpy, { params: {} }, {}); + + expect(callbackSpy.calledOnce).to.be.true; + expect(reqBidsConfig.ortb2Fragments.global).to.have.own.property('device'); + expect(reqBidsConfig.ortb2Fragments.global.device).to.have.own.property('ext'); + expect(reqBidsConfig.ortb2Fragments.global.device.ext).to.have.own.property('hmns').which.is.an('object').that.deep.equals(hmnsData); + }); + }); + + describe('Sumbodule execution', function() { + let sandbox2; + let submoduleStub; + beforeEach(function() { + sandbox2 = sinon.sandbox.create(); + submoduleStub = sandbox2.stub(hook, 'submodule'); + }); + afterEach(function () { + sandbox2.restore(); + }); + + function getModule() { + main(); + + expect(submoduleStub.calledOnceWith('realTimeData')).to.equal(true); + + const submoduleDef = submoduleStub.getCall(0).args[1]; + expect(submoduleDef).to.be.an('object'); + expect(submoduleDef).to.have.own.property('name', SUBMODULE_NAME); + expect(submoduleDef).to.have.own.property('init').that.is.a('function'); + expect(submoduleDef).to.have.own.property('getBidRequestData').that.is.a('function'); + + return submoduleDef; + } + + it('should register humansecurity RTD submodule provider', function () { + getModule(); + }); + + it('should refuse initialization on invalid customer id configuration', function () { + const { init } = getModule(); + expect(init({ params: { clientId: 123 } })).to.equal(false); + expect(loadExternalScriptStub.notCalled).to.be.true; + }); + + it('should commence initialization on valid initialization', function () { + const { init } = getModule(); + expect(init({ params: { clientId: 'customer123' } })).to.equal(true); + expect(loadExternalScriptStub.calledOnce).to.be.true; + }); + + it('should commence initialization on default initialization', function () { + const { init } = getModule(); + expect(init({ })).to.equal(true); + expect(loadExternalScriptStub.calledOnce).to.be.true; + }); + }); +}); diff --git a/test/spec/modules/id5AnalyticsAdapter_spec.js b/test/spec/modules/id5AnalyticsAdapter_spec.js index c9d21daa4e0..7616052dbe7 100644 --- a/test/spec/modules/id5AnalyticsAdapter_spec.js +++ b/test/spec/modules/id5AnalyticsAdapter_spec.js @@ -102,7 +102,7 @@ describe('ID5 analytics adapter', () => { server.respond(); // Why 3? 1: config, 2: tcfEnforcement, 3: auctionEnd - // tcfEnforcement? yes, gdprEnforcement module emits in reaction to auctionEnd + // tcfEnforcement? yes, tcfControl module emits in reaction to auctionEnd expect(server.requests).to.have.length(3); const body1 = JSON.parse(server.requests[1].requestBody); diff --git a/test/spec/modules/id5IdSystem_spec.js b/test/spec/modules/id5IdSystem_spec.js index 1da862cc007..456bd92cb02 100644 --- a/test/spec/modules/id5IdSystem_spec.js +++ b/test/spec/modules/id5IdSystem_spec.js @@ -1,9 +1,10 @@ import * as id5System from '../../../modules/id5IdSystem.js'; import { + attachIdSystem, coreStorage, getConsentHash, init, - requestBidsHook, + startAuctionHook, setSubmoduleRegistry } from '../../../modules/userId/index.js'; import {config} from '../../../src/config.js'; @@ -17,6 +18,7 @@ import {mockGdprConsent} from '../../helpers/consentData.js'; import {server} from '../../mocks/xhr.js'; import {expect} from 'chai'; import {GreedyPromise} from '../../../src/utils/promise.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const IdFetchFlow = id5System.IdFetchFlow; @@ -24,6 +26,7 @@ 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 TRUE_LINK_SOURCE = 'true-link-id5-sync.com'; const ID5_TEST_PARTNER_ID = 173; const ID5_ENDPOINT = `https://id5-sync.com/g/v2/${ID5_TEST_PARTNER_ID}.json`; const ID5_API_CONFIG_URL = `https://id5-sync.com/api/config/prebid`; @@ -46,10 +49,8 @@ describe('ID5 ID System', function () { const EUID_STORED_ID = 'EUID_1'; const EUID_SOURCE = 'uidapi.com'; const ID5_STORED_OBJ_WITH_EUID = { - 'universal_uid': ID5_STORED_ID, - 'signature': ID5_STORED_SIGNATURE, + ...ID5_STORED_OBJ, 'ext': { - 'linkType': ID5_STORED_LINK_TYPE, 'euid': { 'source': EUID_SOURCE, 'uids': [{ @@ -59,6 +60,11 @@ describe('ID5 ID System', function () { } } }; + const TRUE_LINK_STORED_ID = 'TRUE_LINK_1'; + const ID5_STORED_OBJ_WITH_TRUE_LINK = { + ...ID5_STORED_OBJ, + publisherTrueLinkId: TRUE_LINK_STORED_ID + }; const ID5_RESPONSE_ID = 'newid5id'; const ID5_RESPONSE_SIGNATURE = 'abcdef'; const ID5_RESPONSE_LINK_TYPE = 2; @@ -87,6 +93,15 @@ describe('ID5 ID System', function () { 'Content-Type': 'application/json' }; + function expDaysStr(expDays) { + return (new Date(Date.now() + (1000 * 60 * 60 * 24 * expDays))).toUTCString(); + } + + function storeInStorage(key, value, expDays) { + id5System.storage.setDataInLocalStorage(`${key}_exp`, expDaysStr(expDays)); + id5System.storage.setDataInLocalStorage(`${key}`, value); + } + function getId5FetchConfig(partner = ID5_TEST_PARTNER_ID, storageName = id5System.ID5_STORAGE_NAME, storageType = 'html5') { return { name: ID5_MODULE_NAME, @@ -146,6 +161,16 @@ describe('ID5 ID System', function () { }); } + function wrapAsyncExpects(done, expectsFn) { + return function () { + try { + expectsFn(); + } catch (err) { + done(err); + } + } + } + class XhrServerMock { currentRequestIdx = 0; server; @@ -621,8 +646,6 @@ describe('ID5 ID System', function () { it('should call the ID5 server with nb=1 when no stored value exists and reset after', async function () { const xhrServerMock = new XhrServerMock(server); - const TEST_PARTNER_ID = 189; - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(TEST_PARTNER_ID)); // Trigger the fetch but we await on it later const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); @@ -632,28 +655,28 @@ describe('ID5 ID System', function () { expect(requestBody.nbPage).is.eq(1); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - await submoduleResponsePromise; + const response = await submoduleResponsePromise; - expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); + expect(response.nbPage).is.undefined; }); it('should call the ID5 server with incremented nb when stored value exists and reset after', async function () { const xhrServerMock = new XhrServerMock(server); const TEST_PARTNER_ID = 189; const config = getId5FetchConfig(TEST_PARTNER_ID); - id5System.storeNbInCache(TEST_PARTNER_ID, 1); + const storedObj = {...ID5_STORED_OBJ, nbPage: 1}; // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(config, undefined, ID5_STORED_OBJ); + const submoduleResponsePromise = callSubmoduleGetId(config, undefined, storedObj); const fetchRequest = await xhrServerMock.expectFetchRequest(); const requestBody = JSON.parse(fetchRequest.requestBody); expect(requestBody.nbPage).is.eq(2); fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); - await submoduleResponsePromise; + const response = await submoduleResponsePromise; - expect(id5System.getNbFromCache(TEST_PARTNER_ID)).is.eq(0); + expect(response.nbPage).is.undefined; }); it('should call the ID5 server with ab_testing object when abTesting is turned on', async function () { @@ -704,45 +727,6 @@ describe('ID5 ID System', function () { await submoduleResponsePromise; }); - it('should store the privacy object from the ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server); - - // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - - const privacy = { - jurisdiction: 'gdpr', - id5_consent: true - }; - - const fetchRequest = await xhrServerMock.expectFetchRequest(); - const responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = privacy; - - fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); - await submoduleResponsePromise; - - expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.eq(JSON.stringify(privacy)); - coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); - }); - - it('should not store a privacy object if not part of ID5 server response', async function () { - const xhrServerMock = new XhrServerMock(server); - coreStorage.removeDataFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME); - - // Trigger the fetch but we await on it later - const submoduleResponsePromise = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); - - const fetchRequest = await xhrServerMock.expectFetchRequest(); - const responseObject = utils.deepClone(ID5_JSON_RESPONSE); - responseObject.privacy = undefined; - - fetchRequest.respond(200, responseHeader, JSON.stringify(responseObject)); - await submoduleResponsePromise; - - expect(id5System.getFromLocalStorage(id5System.ID5_PRIVACY_STORAGE_NAME)).is.null; - }); - describe('with successful external module call', function () { const MOCK_RESPONSE = { ...ID5_JSON_RESPONSE, @@ -835,6 +819,38 @@ describe('ID5 ID System', function () { id5System.id5IdSubmodule.getId(getId5FetchConfig()); }); }); + + it('should pass true link info to ID5 server even when true link is not booted', function () { + let xhrServerMock = new XhrServerMock(server); + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal({booted: false}); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); + + it('should pass full true link info to ID5 server when true link is booted', function () { + let xhrServerMock = new XhrServerMock(server); + let trueLinkResponse = {booted: true, redirected: true, id: 'TRUE_LINK_ID'}; + window.id5Bootstrap = { + getTrueLinkInfo: function () { + return trueLinkResponse; + } + }; + let submoduleResponse = callSubmoduleGetId(getId5FetchConfig(), undefined, ID5_STORED_OBJ); + + return xhrServerMock.expectFetchRequest() + .then(fetchRequest => { + let requestBody = JSON.parse(fetchRequest.requestBody); + expect(requestBody.true_link).is.deep.equal(trueLinkResponse); + fetchRequest.respond(200, responseHeader, JSON.stringify(ID5_JSON_RESPONSE)); + return submoduleResponse; + }); + }); }); describe('Local storage', () => { @@ -878,7 +894,6 @@ describe('ID5 ID System', function () { sinon.stub(events, 'getEvents').returns([]); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); coreStorage.setDataInLocalStorage(id5System.ID5_STORAGE_NAME + '_cst', getConsentHash()); adUnits = [getAdUnitMock()]; }); @@ -886,19 +901,18 @@ describe('ID5 ID System', function () { events.getEvents.restore(); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME); coreStorage.removeDataFromLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); coreStorage.removeDataFromLocalStorage(id5System.ID5_STORAGE_NAME + '_cst'); sandbox.restore(); }); it('should add stored ID from cache to bids', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(function () { + startAuctionHook(wrapAsyncExpects(done, () => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); @@ -916,17 +930,17 @@ describe('ID5 ID System', function () { }); }); done(); - }, {adUnits}); + }), {adUnits}); }); it('should add stored EUID from cache to bids', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_EUID), 1); init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getFetchLocalStorageConfig()); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.euid`); @@ -948,12 +962,37 @@ describe('ID5 ID System', function () { }, {adUnits}); }); + it('should add stored TRUE_LINK_ID from cache to bids', function (done) { + storeInStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ_WITH_TRUE_LINK), 1); + + init(config); + setSubmoduleRegistry([id5System.id5IdSubmodule]); + config.setConfig(getFetchLocalStorageConfig()); + + startAuctionHook(wrapAsyncExpects(done, function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property(`userId.trueLinkId`); + expect(bid.userId.trueLinkId.uid).is.equal(TRUE_LINK_STORED_ID); + expect(bid.userIdAsEids[1]).is.deep.equal({ + source: TRUE_LINK_SOURCE, + uids: [{ + id: TRUE_LINK_STORED_ID, + atype: 1, + }] + }); + }); + }); + done(); + }), {adUnits}); + }); + it('should add config value ID to bids', function (done) { init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(getValueConfig(ID5_STORED_ID)); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property(`userId.${ID5_EIDS_NAME}`); @@ -968,49 +1007,22 @@ describe('ID5 ID System', function () { }, {adUnits}); }); - it('should set nb=1 in cache when no stored nb value exists and cached ID', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - coreStorage.removeDataFromLocalStorage(id5System.nbCacheName(ID5_TEST_PARTNER_ID)); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - requestBidsHook((adUnitConfig) => { - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(1); - done(); - }, {adUnits}); - }); - - it('should increment nb in cache when stored nb value exists and cached ID', function (done) { - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, JSON.stringify(ID5_STORED_OBJ), 1); - id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); - - init(config); - setSubmoduleRegistry([id5System.id5IdSubmodule]); - config.setConfig(getFetchLocalStorageConfig()); - - requestBidsHook(() => { - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(2); - done(); - }, {adUnits}); - }); - it('should call ID5 servers with signature and incremented nb post auction if refresh needed', function () { const xhrServerMock = new XhrServerMock(server); - const initialLocalStorageValue = JSON.stringify(ID5_STORED_OBJ); - id5System.storeInLocalStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); - id5System.storeInLocalStorage(`${id5System.ID5_STORAGE_NAME}_last`, id5System.expDaysStr(-1), 1); + let storedObject = ID5_STORED_OBJ; + storedObject.nbPage = 1; + const initialLocalStorageValue = JSON.stringify(storedObject); + storeInStorage(id5System.ID5_STORAGE_NAME, initialLocalStorageValue, 1); + storeInStorage(`${id5System.ID5_STORAGE_NAME}_last`, expDaysStr(-1), 1); - id5System.storeNbInCache(ID5_TEST_PARTNER_ID, 1); let id5Config = getFetchLocalStorageConfig(); id5Config.userSync.userIds[0].storage.refreshInSeconds = 2; + id5Config.userSync.auctionDelay = 0; // do not trigger callback before auction init(config); setSubmoduleRegistry([id5System.id5IdSubmodule]); config.setConfig(id5Config); - return new Promise((resolve) => { - requestBidsHook(() => { + startAuctionHook(() => { resolve(); }, {adUnits}); }).then(() => { @@ -1021,18 +1033,7 @@ describe('ID5 ID System', function () { const requestBody = JSON.parse(request.requestBody); expect(requestBody.s).is.eq(ID5_STORED_SIGNATURE); expect(requestBody.nbPage).is.eq(2); - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); request.respond(200, HEADERS_CONTENT_TYPE_JSON, JSON.stringify(ID5_JSON_RESPONSE)); - - return new Promise(function (resolve) { - (function waitForCondition() { - if (id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME) !== initialLocalStorageValue) return resolve(); - setTimeout(waitForCondition, 30); - })(); - }); - }).then(() => { - expect(decodeURIComponent(id5System.getFromLocalStorage(id5System.ID5_STORAGE_NAME))).is.eq(JSON.stringify(ID5_JSON_RESPONSE)); - expect(id5System.getNbFromCache(ID5_TEST_PARTNER_ID)).is.eq(0); }); }); }); @@ -1053,6 +1054,11 @@ describe('ID5 ID System', function () { 'ext': {'provider': ID5_SOURCE} }); }); + it('should decode trueLinkId from a stored object with trueLinkId', function () { + expect(id5System.id5IdSubmodule.decode(ID5_STORED_OBJ_WITH_TRUE_LINK, getId5FetchConfig()).trueLinkId).is.deep.equal({ + 'uid': TRUE_LINK_STORED_ID + }); + }); }); describe('A/B Testing', function () { @@ -1123,4 +1129,45 @@ describe('ID5 ID System', function () { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(id5System); + }); + 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 + } + }] + }); + }); + }) }); diff --git a/test/spec/modules/idImportLibrary_spec.js b/test/spec/modules/idImportLibrary_spec.js index d5b3e32546d..6045eb0bda0 100644 --- a/test/spec/modules/idImportLibrary_spec.js +++ b/test/spec/modules/idImportLibrary_spec.js @@ -4,6 +4,10 @@ import * as idImportlibrary from 'modules/idImportLibrary.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {config} from 'src/config.js'; import {hook} from '../../../src/hook.js'; +import * as activities from '../../../src/activities/rules.js'; +import { ACTIVITY_ENRICH_UFPD } from '../../../src/activities/activities.js'; +import { CONF_DEFAULT_FULL_BODY_SCAN, CONF_DEFAULT_INPUT_SCAN } from '../../../modules/idImportLibrary.js'; + var expect = require('chai').expect; const mockMutationObserver = { @@ -86,6 +90,16 @@ describe('IdImportLibrary Tests', function () { idImportlibrary.setConfig(config); expect(config.inputscan).to.be.equal(true); }); + it('results when activity is not allowed', function () { + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity) => { + return !(activity === ACTIVITY_ENRICH_UFPD); + }); + let config = { 'url': 'URL', 'debounce': 0 }; + idImportlibrary.setConfig(config); + sinon.assert.called(utils.logError); + expect(config.inputscan).to.be.not.equal(CONF_DEFAULT_INPUT_SCAN); + expect(config.fullscan).to.be.not.equal(CONF_DEFAULT_FULL_BODY_SCAN); + }); }); describe('Test with email is found', function () { let mutationObserverStub; diff --git a/test/spec/modules/idWardRtdProvider_spec.js b/test/spec/modules/idWardRtdProvider_spec.js deleted file mode 100644 index d1601f058ff..00000000000 --- a/test/spec/modules/idWardRtdProvider_spec.js +++ /dev/null @@ -1,116 +0,0 @@ -import {config} from 'src/config.js'; -import {getRealTimeData, idWardRtdSubmodule, storage} from 'modules/idWardRtdProvider.js'; - -describe('idWardRtdProvider', function() { - let getDataFromLocalStorageStub; - - const testReqBidsConfigObj = { - adUnits: [ - { - bids: ['bid1', 'bid2'] - } - ] - }; - - const onDone = function() { return true }; - - const cmoduleConfig = { - 'name': 'idWard', - 'params': { - 'cohortStorageKey': 'cohort_ids' - } - } - - beforeEach(function() { - config.resetConfig(); - getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage') - }); - - afterEach(function () { - getDataFromLocalStorageStub.restore(); - }); - - describe('idWardRtdSubmodule', function() { - it('successfully instantiates', function () { - expect(idWardRtdSubmodule.init()).to.equal(true); - }); - }); - - describe('Get Real-Time Data', function() { - it('gets rtd from local storage', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = { - ortb2Fragments: { - global: {} - } - }; - - const rtdUserObj1 = { - name: 'anonymised.io', - ext: { - segtax: 503 - }, - segment: [ - { - id: 'TCZPQOWPEJG3MJOTUQUF793A' - }, - { - id: '93SUG3H540WBJMYNT03KX8N3' - } - ] - }; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns(JSON.stringify(['TCZPQOWPEJG3MJOTUQUF793A', '93SUG3H540WBJMYNT03KX8N3'])); - - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(bidConfig.ortb2Fragments.global.user.data).to.deep.include.members([rtdUserObj1]); - }); - - it('do not set rtd if local storage empty', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = {}; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns(null); - - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2).to.be.undefined; - }); - - it('do not set rtd if local storage has incorrect value', function() { - const rtdConfig = { - params: { - cohortStorageKey: 'cohort_ids', - segtax: 503 - } - }; - - const bidConfig = {}; - - getDataFromLocalStorageStub.withArgs('cohort_ids') - .returns('wrong cohort ids value'); - - expect(config.getConfig().ortb2).to.be.undefined; - getRealTimeData(bidConfig, () => {}, rtdConfig, {}); - expect(config.getConfig().ortb2).to.be.undefined; - }); - - it('should initialize and return with config', function () { - expect(getRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig)).to.equal(undefined) - }); - }); -}); diff --git a/test/spec/modules/identityLinkIdSystem_spec.js b/test/spec/modules/identityLinkIdSystem_spec.js index 66d5a3edd00..5efe9794f92 100644 --- a/test/spec/modules/identityLinkIdSystem_spec.js +++ b/test/spec/modules/identityLinkIdSystem_spec.js @@ -4,6 +4,9 @@ import {server} from 'test/mocks/xhr.js'; import {getCoreStorageManager} from '../../../src/storageManager.js'; import {stub} from 'sinon'; import { gppDataHandler } from '../../../src/adapterManager.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const storage = getCoreStorageManager(); @@ -238,4 +241,21 @@ describe('IdentityLinkId tests', function () { expect(envelopeValueFromStorage).to.be.a('string'); expect(envelopeValueFromStorage).to.be.eq(testEnvelopeValue); }) + + describe('eid', () => { + before(() => { + attachIdSystem(identityLinkSubmodule); + }); + it('identityLink', function() { + const userId = { + idl_env: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveramp.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + }) }); diff --git a/test/spec/modules/idxIdSystem_spec.js b/test/spec/modules/idxIdSystem_spec.js index 56e1c709c8b..bfe9d1b1e68 100644 --- a/test/spec/modules/idxIdSystem_spec.js +++ b/test/spec/modules/idxIdSystem_spec.js @@ -1,9 +1,10 @@ import { expect } from 'chai'; import {find} from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { init, startAuctionHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, idxIdSubmodule } from 'modules/idxIdSystem.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; +import 'src/prebid.js'; const IDX_COOKIE_NAME = '_idx'; const IDX_DUMMY_VALUE = 'idx value for testing'; @@ -100,10 +101,15 @@ describe('IDx ID System', () => { afterEach(() => { sandbox.restore(); + config.resetConfig(); + }) + + after(() => { + init(config); }) it('when a stored IDx exists it is added to bids', (done) => { - requestBidsHook(() => { + startAuctionHook(() => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.idx'); diff --git a/test/spec/modules/illuminBidAdapter_spec.js b/test/spec/modules/illuminBidAdapter_spec.js index 9b702c027f9..3cd79c7468d 100644 --- a/test/spec/modules/illuminBidAdapter_spec.js +++ b/test/spec/modules/illuminBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/illuminBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,14 +18,9 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/illuminBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'exchange'; @@ -104,9 +107,15 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -325,7 +334,14 @@ describe('IlluminBidAdapter', function () { startdelay: 0 } }, - gpid: '0123456789' + gpid: '0123456789', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -387,6 +403,13 @@ describe('IlluminBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentData: [], + contentLang: 'en', + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -510,8 +533,6 @@ describe('IlluminBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -564,13 +585,13 @@ describe('IlluminBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -578,7 +599,7 @@ describe('IlluminBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -602,8 +623,8 @@ describe('IlluminBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -614,7 +635,7 @@ describe('IlluminBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/imdsBidAdapter_spec.js b/test/spec/modules/imdsBidAdapter_spec.js index b71a0bc51d9..2911ee588c0 100644 --- a/test/spec/modules/imdsBidAdapter_spec.js +++ b/test/spec/modules/imdsBidAdapter_spec.js @@ -585,7 +585,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -622,7 +622,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -651,7 +651,7 @@ describe('imdsBidAdapter ', function () { playerSize: [[640, 480]], startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] } }, @@ -680,7 +680,7 @@ describe('imdsBidAdapter ', function () { maxduration: 45, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'], protocols: [1], api: 1 @@ -703,7 +703,7 @@ describe('imdsBidAdapter ', function () { playerSize: [[ 640, 480 ]], startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] } }, @@ -726,7 +726,7 @@ describe('imdsBidAdapter ', function () { w: 640, startdelay: 1, linearity: 1, - placement: 1, + plcmt: 1, mimes: ['video/mp4'] }, id: 'v2624fabbb078e8-640x480', diff --git a/test/spec/modules/improvedigitalBidAdapter_spec.js b/test/spec/modules/improvedigitalBidAdapter_spec.js index a955b716584..46e07bacbe4 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -10,12 +10,13 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; +import * as prebidGlobal from 'src/prebidGlobal.js'; describe('Improve Digital Adapter Tests', function () { const METHOD = 'POST'; @@ -32,6 +33,7 @@ describe('Improve Digital Adapter Tests', function () { const simpleBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 1053688 }, adUnitCode: 'div-gpt-ad-1499748733608-0', @@ -59,6 +61,7 @@ describe('Improve Digital Adapter Tests', function () { const instreamBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 123456 }, adUnitCode: 'video1', @@ -107,17 +110,6 @@ describe('Improve Digital Adapter Tests', function () { } }; - const simpleSmartTagBidRequest = { - mediaTypes: {}, - bidder: 'improvedigital', - bidId: '1a2b3c', - placementCode: 'placement1', - params: { - publisherId: 1032, - placementKey: 'data_team_test_hb_smoke_test' - } - }; - const bidderRequest = { ortb2: { source: { @@ -174,6 +166,10 @@ describe('Improve Digital Adapter Tests', function () { return bidRequests; } + function formatPublisherUrl(baseUrl, publisherId) { + return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; + } + before(() => { hook.ready(); }); @@ -188,12 +184,7 @@ describe('Improve Digital Adapter Tests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return false when both placementId and placementKey + publisherId are missing', function () { - const bid = { 'params': {} }; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should return false when only one of placementKey and publisherId is present', function () { + it('should return false when only one of placementId or publisherId is present', function () { let bid = { params: { publisherId: 1234 @@ -202,38 +193,38 @@ describe('Improve Digital Adapter Tests', function () { expect(spec.isBidRequestValid(bid)).to.equal(false); bid = { params: { - placementKey: 'xyz' + placementId: 1234 } }; expect(spec.isBidRequestValid(bid)).to.equal(false); }); - it('should return true when placementId is passed', function () { + it('should return true when both placementId and publisherId are passed', function () { expect(spec.isBidRequestValid(simpleBidRequest)).to.equal(true); }); - - it('should return true when both placementKey and publisherId are passed', function () { - expect(spec.isBidRequestValid(simpleSmartTagBidRequest)).to.equal(true); - }); }); describe('buildRequests', function () { let getConfigStub = null; + let getGlobalStub = null; afterEach(function () { if (getConfigStub) { getConfigStub.restore(); getConfigStub = null; } + + if (getGlobalStub) { + getGlobalStub.restore(); + getGlobalStub = null; + } }); it('should make a well-formed request objects', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); @@ -247,7 +238,7 @@ describe('Improve Digital Adapter Tests', function () { sinon.assert.match(payload.imp, [ sinon.match({ id: '33e9500b21129f', - secure: 0, + secure: 1, ext: { bidder: { placementId: 1053688, @@ -264,19 +255,17 @@ describe('Improve Digital Adapter Tests', function () { }); it('should make a well-formed request object for multi-format ad unit', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests(updateNativeParams([multiFormatBidRequest]), multiFormatBidderRequest)[0]; expect(request).to.be.an('object'); expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const payload = JSON.parse(request.data); expect(payload).to.be.an('object'); sinon.assert.match(payload.imp, [ sinon.match({ id: '33e9500b21129f', - secure: 0, + secure: 1, ext: { bidder: { placementId: 1053688, @@ -284,7 +273,6 @@ describe('Improve Digital Adapter Tests', function () { }, ...(FEATURES.VIDEO && { video: { - placement: OUTSTREAM_TYPE, w: 640, h: 480, mimes: ['video/mp4'], @@ -345,12 +333,6 @@ describe('Improve Digital Adapter Tests', function () { }); } - it('should set placementKey and publisherId for smart tags', function () { - const payload = JSON.parse(spec.buildRequests([simpleSmartTagBidRequest], bidderRequest)[0].data); - expect(payload.imp[0].ext.bidder.publisherId).to.equal(1032); - expect(payload.imp[0].ext.bidder.placementKey).to.equal('data_team_test_hb_smoke_test'); - }); - it('should add keyValues', function () { const bidRequest = Object.assign({}, simpleBidRequest); const keyValues = { @@ -374,10 +356,21 @@ describe('Improve Digital Adapter Tests', function () { } }); - it('should add bid floor', function () { - const bidRequest = Object.assign({}, simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + it('should add bid floor correctly', function () { + getGlobalStub = sinon.stub(prebidGlobal, 'getGlobal').returns({ + convertCurrency: (cpm, from, to) => { + const conversionKeys = { 'EUR-USD': 1.75 }; + const conversionRate = conversionKeys[`${from}-${to}`]; + if (!conversionRate) { + throw new Error(`No conversion rate found for ${from}-${to}`); + } + return cpm * conversionRate; + } + }); + const bidRequest = deepClone(simpleBidRequest); + // Floor price currency shouldn't be populated without a floor price + let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloorcur).to.not.exist; // Default floor price currency @@ -386,18 +379,25 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].bidfloor).to.equal(0.05); expect(payload.imp[0].bidfloorcur).to.equal('USD'); - // Floor price currency - bidRequest.params.bidFloorCur = 'eUR'; + // Floor price sent as is when currency cannot be converted to default bid adapter currency + bidRequest.params.bidFloorCur = 'UAH'; + bidRequest.params.bidFloor = 0.05; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloor).to.equal(0.05); - expect(payload.imp[0].bidfloorcur).to.equal('EUR'); + expect(payload.imp[0].bidfloorcur).to.equal('UAH'); + + // Floor price currency converted to default bid adapter currency + bidRequest.params.bidFloorCur = 'eUR'; + payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); + expect(payload.imp[0].bidfloor).to.equal(0.08750000000000001); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); // getFloor defined -> use it over bidFloor let getFloorResponse = { currency: 'USD', floor: 3 }; bidRequest.getFloor = () => getFloorResponse; payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); expect(payload.imp[0].bidfloor).to.equal(3); - // expect(payload.imp[0].bidfloorcur).to.equal('USD'); + expect(payload.imp[0].bidfloorcur).to.equal('USD'); }); it('should add GDPR consent string', function () { @@ -405,8 +405,7 @@ describe('Improve Digital Adapter Tests', function () { const payload = JSON.parse(spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(bidderRequestGdpr))[0].data); expect(payload.regs.ext.gdpr).to.exist.and.to.equal(1); expect(payload.user.ext.consent).to.equal('CONSENT'); - expect(payload.user.ext.ConsentedProvidersSettings).to.not.exist; - expect(payload.user.ext.consented_providers_settings.consented_providers).to.exist.and.to.deep.equal([1, 35, 41, 101]); + expect(payload.user.ext.ConsentedProvidersSettings.consented_providers).to.exist.and.to.deep.equal('1~1.35.41.101'); }); it('should not add consented providers when empty', function () { @@ -475,25 +474,6 @@ describe('Improve Digital Adapter Tests', function () { }); if (FEATURES.VIDEO) { - it('should add correct placement value for instream and outstream video', function () { - let bidRequest = deepClone(simpleBidRequest); - let payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video).to.not.exist; - - bidRequest = deepClone(simpleBidRequest); - bidRequest.mediaTypes = { - video: { - context: 'instream', - playerSize: [640, 480] - } - }; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(1); - bidRequest.mediaTypes.video.context = 'outstream'; - payload = JSON.parse(spec.buildRequests([bidRequest], bidderRequest)[0].data); - expect(payload.imp[0].video.placement).to.exist.and.equal(3); - }); - it('should set video params for instream', function() { const bidRequest = deepClone(instreamBidRequest); delete bidRequest.mediaTypes.video.playerSize; @@ -508,13 +488,12 @@ describe('Improve Digital Adapter Tests', function () { minbitrate: 500, maxbitrate: 2000, w: 1024, - h: 640, - placement: INSTREAM_TYPE, + h: 640 }; bidRequest.params.video = videoParams; const request = spec.buildRequests([bidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); - expect(payload.imp[0].video).to.deep.equal(videoParams); + expect(payload.imp[0].video).to.deep.include(videoParams); }); it('should set video playerSize over video params', () => { @@ -551,7 +530,6 @@ describe('Improve Digital Adapter Tests', function () { const payload = JSON.parse(request.data); expect(payload.imp[0].video).to.deep.equal({...{ mimes: ['video/mp4'], - placement: OUTSTREAM_TYPE, w: bidRequest.mediaTypes.video.playerSize[0], h: bidRequest.mediaTypes.video.playerSize[1], }, @@ -564,7 +542,6 @@ describe('Improve Digital Adapter Tests', function () { const request = spec.buildRequests([bidRequest], {})[0]; const payload = JSON.parse(request.data); const testVideoParams = Object.assign({ - placement: OUTSTREAM_TYPE, w: 640, h: 480, mimes: ['video/mp4'], @@ -583,7 +560,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add eids', function () { - const userIdAsEids = [ + const eids = [ { source: 'id5-sync.com', uids: [{ @@ -599,9 +576,10 @@ describe('Improve Digital Adapter Tests', function () { id: '1111' }] }]}}; - const bidRequest = Object.assign({}, simpleBidRequest); - bidRequest.userIdAsEids = userIdAsEids; - const request = spec.buildRequests([bidRequest], bidderRequestReferrer)[0]; + const request = spec.buildRequests([simpleBidRequest], { + ...bidderRequestReferrer, + ortb2: {user: {ext: {eids: eids}}} + })[0]; const payload = JSON.parse(request.data); expect(payload.user).to.deep.equal(expectedUserObject); }); @@ -609,7 +587,7 @@ describe('Improve Digital Adapter Tests', function () { it('should return 2 requests', function () { const requests = spec.buildRequests([ simpleBidRequest, - simpleSmartTagBidRequest + instreamBidRequest ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); @@ -621,7 +599,7 @@ describe('Improve Digital Adapter Tests', function () { const requests = spec.buildRequests([ simpleBidRequest, instreamBidRequest ], bidderRequest); expect(requests).to.be.an('array'); expect(requests.length).to.equal(1); - expect(requests[0].url).to.equal(AD_SERVER_URL); + expect(requests[0].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const request = JSON.parse(requests[0].data); expect(request.imp.length).to.equal(2); expect(request.imp[0].banner).to.exist; @@ -635,7 +613,7 @@ describe('Improve Digital Adapter Tests', function () { expect(requests).to.be.an('array'); expect(requests.length).to.equal(2); expect(requests[0].url).to.equal(EXTEND_URL); - expect(requests[1].url).to.equal(AD_SERVER_URL); + expect(requests[1].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const adServerRequest = JSON.parse(requests[1].data); expect(adServerRequest.imp.length).to.equal(2); expect(adServerRequest.imp[0].banner).to.exist; @@ -643,8 +621,6 @@ describe('Improve Digital Adapter Tests', function () { }); it('should set Prebid sizes in bid request', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const request = spec.buildRequests([simpleBidRequest], bidderRequest)[0]; const payload = JSON.parse(request.data); sinon.assert.match(payload.imp[0].banner, { @@ -656,8 +632,6 @@ describe('Improve Digital Adapter Tests', function () { }); it('should not add single size filter when using Prebid sizes', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('improvedigital.usePrebidSizes').returns(true); const bidRequest = Object.assign({}, simpleBidRequest); const size = { w: 800, @@ -683,32 +657,9 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.app.content).does.exist.and.equal('XYZ'); }); - it('should not set site when app is defined in CONFIG', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('app').returns({ content: 'XYZ' }); - let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; - let payload = JSON.parse(request.data); - expect(payload.site).does.not.exist; - expect(payload.app).does.exist; - expect(payload.app.content).does.exist.and.equal('XYZ'); - }); - it('should set correct site params', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('site').returns({ - content: 'XYZ', - page: 'https://improveditigal.com/', - domain: 'improveditigal.com' - }); let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; let payload = JSON.parse(request.data); - expect(payload.site.content).does.exist.and.equal('XYZ'); - expect(payload.site.page).does.exist.and.equal('https://improveditigal.com/'); - expect(payload.site.domain).does.exist.and.equal('improveditigal.com'); - getConfigStub.reset(); - - request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequestReferrer))[0]; - payload = JSON.parse(request.data); expect(payload.site.content).does.not.exist; expect(payload.site.page).does.exist.and.equal('https://blah.com/test.html'); expect(payload.site.domain).does.exist.and.equal('blah.com'); @@ -721,23 +672,13 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.site.domain).does.exist.and.equal('blah.com'); }); - it('should set site when app not available', function () { - getConfigStub = sinon.stub(config, 'getConfig'); - getConfigStub.withArgs('app').returns(undefined); - getConfigStub.withArgs('site').returns({}); - let request = spec.buildRequests([simpleBidRequest], syncAddFPDToBidderRequest(bidderRequest))[0]; - let payload = JSON.parse(request.data); - expect(payload.site).does.exist; - expect(payload.app).does.not.exist; - }); - it('should call basic ads endpoint when no consent for purpose 1', function () { const consent = deepClone(gdprConsent); deepSetValue(consent, 'vendorData.purpose.consents.1', false); const bidderRequestWithConsent = deepClone(bidderRequest); bidderRequestWithConsent.gdprConsent = consent; const request = spec.buildRequests([simpleBidRequest], bidderRequestWithConsent)[0]; - expect(request.url).to.equal(BASIC_ADS_URL); + expect(request.url).to.equal(formatPublisherUrl(BASIC_ADS_BASE_URL, 1234)); }); it('should set extend params when extend mode enabled from global configuration', function () { @@ -756,6 +697,7 @@ describe('Improve Digital Adapter Tests', function () { expect(payload.imp[0].ext.bidder).to.not.exist; expect(payload.imp[0].ext.prebid.bidder.improvedigital).to.deep.equal({ placementId: 1053688, + publisherId: 1234, keyValues }); expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal('1053688'); @@ -781,18 +723,15 @@ describe('Improve Digital Adapter Tests', function () { bidRequest.params.extend = false; getConfigStub.withArgs('improvedigital.extend').returns(true); request = spec.buildRequests([bidRequest], { bids: [bidRequest] })[0]; - expect(request.url).to.equal(AD_SERVER_URL); + expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); const requests = spec.buildRequests([bidRequest, instreamBidRequest], { bids: [bidRequest, instreamBidRequest] }); expect(requests.length).to.equal(2); - expect(requests[0].url).to.equal(AD_SERVER_URL); + expect(requests[0].url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1234)); expect(requests[1].url).to.equal(EXTEND_URL); }); it('should add publisherId to request URL when available in request params', function() { - function formatPublisherUrl(baseUrl, publisherId) { - return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; - } const bidRequest = deepClone(simpleBidRequest); bidRequest.params.publisherId = 1000; let request = spec.buildRequests([bidRequest], bidderRequest)[0]; @@ -841,10 +780,6 @@ describe('Improve Digital Adapter Tests', function () { bidderRequestWithConsent.gdprConsent = consent; request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; expect(request.url).to.equal(formatPublisherUrl(AD_SERVER_BASE_URL, 1000)); - - delete bidRequest.params.publisherId; - request = spec.buildRequests([bidRequest], bidderRequestWithConsent)[0]; - expect(request.url).to.equal(AD_SERVER_URL); }); }); @@ -1071,7 +1006,7 @@ describe('Improve Digital Adapter Tests', function () { width: 728, height: 90, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1081,7 +1016,7 @@ describe('Improve Digital Adapter Tests', function () { const multiFormatExpectedBid = [ Object.assign({}, expectedBid[0], { - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]},"native":{},"video":{"context":"outstream","playerSize":[640,480]}},"sizes":[[300,250],[160,600]],"nativeParams":{"body":{"required":true}}},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"35adfe19-d6e9-46b9-9f7d-20da7026b965","cpm":1.9200543539802946,"currency":"EUR","width":728,"height":90,"creative_id":"510265","creativeId":"510265","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', }) ]; @@ -1094,7 +1029,7 @@ describe('Improve Digital Adapter Tests', function () { width: 300, height: 250, ttl: 300, - ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"placementId":1053688,"keyValues":{"testKey":["testValue"]},"bidFloor":0.05,"bidFloorCur":"eUR","size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', + ad: '\x3Cscript>window.__razr_config = {"prebid":{"bidRequest":{"bidder":"improvedigital","params":{"publisherId":1234,"placementId":1053688,"keyValues":{"testKey":["testValue"]},"size":{"w":800,"h":600}},"adUnitCode":"div-gpt-ad-1499748733608-0","transactionId":"f183e871-fbed-45f0-a427-c8a63c4c01eb","bidId":"33e9500b21129f","bidderRequestId":"2772c1e566670b","auctionId":"192721e36a0239","mediaTypes":{"banner":{"sizes":[[300,250],[160,600]]}},"sizes":[[300,250],[160,600]]},"bid":{"mediaType":"banner","ad":"\\"\\"","requestId":"33e9500b21129f","seatBidId":"83c8d524-0955-4d0c-b558-4c9f3600e09b","cpm":1.9200543539802946,"currency":"EUR","width":300,"height":250,"creative_id":"479163","creativeId":"479163","ttl":300,"meta":{},"dealId":320896,"netRevenue":false}}};\x3C/script>  ', creativeId: '479163', dealId: 320896, netRevenue: false, diff --git a/test/spec/modules/imuIdSystem_spec.js b/test/spec/modules/imuIdSystem_spec.js index 3650302a2ed..1d6f79786a0 100644 --- a/test/spec/modules/imuIdSystem_spec.js +++ b/test/spec/modules/imuIdSystem_spec.js @@ -13,6 +13,9 @@ import { } from 'modules/imuIdSystem.js'; import * as utils from 'src/utils.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('imuId module', function () { // let setLocalStorageStub; @@ -181,4 +184,38 @@ describe('imuId module', function () { expect(res.success('error response')).to.equal(undefined); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(imuIdSubmodule); + }); + it('should return the correct EID schema with imuid', function() { + const userId = { + imuid: 'testimuid' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'intimatemerger.com', + uids: [{ + id: 'testimuid', + atype: 1 + }] + }); + }); + + it('should return the correct EID schema with imppid', function() { + const userId = { + imppid: 'imppid-value-imppid-value-imppid-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'ppid.intimatemerger.com', + uids: [{ + id: 'imppid-value-imppid-value-imppid-value', + atype: 1 + }] + }); + }); + }) }); diff --git a/test/spec/modules/incrxBidAdapter_spec.js b/test/spec/modules/incrxBidAdapter_spec.js index 3fb4ffe2cd3..72234c17845 100644 --- a/test/spec/modules/incrxBidAdapter_spec.js +++ b/test/spec/modules/incrxBidAdapter_spec.js @@ -1,14 +1,12 @@ import { expect } from 'chai'; import { spec } from 'modules/incrxBidAdapter.js'; +import { BANNER, VIDEO } from 'src/mediaTypes.js'; -describe('IncrementX', function () { - const METHOD = 'POST'; - const URL = 'https://hb.incrementxserv.com/vzhbidder/bid'; - - const bidRequest = { - bidder: 'IncrementX', +describe('incrementx', function () { + const bannerBidRequest = { + bidder: 'incrementx', params: { - placementId: 'PNX-HB-F796830VCF3C4B' + placementId: 'IX-HB-12345' }, mediaTypes: { banner: { @@ -19,86 +17,125 @@ describe('IncrementX', function () { [300, 250], [300, 600] ], - bidId: 'bid-id-123456', - adUnitCode: 'ad-unit-code-1', - bidderRequestId: 'bidder-request-id-123456', - auctionId: 'auction-id-123456', - transactionId: 'transaction-id-123456' + adUnitCode: 'div-gpt-ad-1460505748561-0', + bidId: '2faedf3e89d123', + bidderRequestId: '1c78fb49cc71c6', + auctionId: 'b4f81e8e36232', + transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fb' + }; + const videoBidRequest = { + bidder: 'incrementx', + params: { + placementId: 'IX-HB-12346' + }, + mediaTypes: { + video: { + context: 'outstream', + playerSize: ['640x480'] + } + }, + adUnitCode: 'div-gpt-ad-1460505748561-1', + bidId: '2faedf3e89d124', + bidderRequestId: '1c78fb49cc71c7', + auctionId: 'b4f81e8e36233', + transactionId: '0d95b2c1-a834-4e50-a962-9b6aa0e1c8fc' }; - describe('isBidRequestValid', function () { - it('should return true where required params found', function () { - expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + it('should return true when required params are found', function () { + expect(spec.isBidRequestValid(bannerBidRequest)).to.equal(true); + expect(spec.isBidRequestValid(videoBidRequest)).to.equal(true); }); }); describe('buildRequests', function () { - let bidderRequest = { + const bidderRequest = { refererInfo: { - page: 'https://www.test.com', - reachedTop: true, - isAmp: false, - numIframes: 0, - stack: [ - 'https://www.test.com' - ], - canonicalUrl: null + page: 'https://someurl.com' } }; - - it('should build correct POST request for banner bid', function () { - const request = spec.buildRequests([bidRequest], bidderRequest)[0]; - expect(request).to.be.an('object'); - expect(request.method).to.equal(METHOD); - expect(request.url).to.equal(URL); - - const payload = JSON.parse(decodeURI(request.data.q)); - expect(payload).to.be.an('object'); - expect(payload._vzPlacementId).to.be.a('string'); - expect(payload.sizes).to.be.an('array'); - expect(payload._slotBidId).to.be.a('string'); - expect(payload._rqsrc).to.be.a('string'); + it('should build banner request', function () { + const requests = spec.buildRequests([bannerBidRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://hb.incrementxserv.com/vzhbidder/bid'); + const data = JSON.parse(decodeURI(requests[0].data.q)); + expect(data._vzPlacementId).to.equal('IX-HB-12345'); + expect(data.sizes).to.to.a('array'); + expect(data.mChannel).to.equal(1); + }); + it('should build video request', function () { + const requests = spec.buildRequests([videoBidRequest], bidderRequest); + expect(requests).to.have.lengthOf(1); + expect(requests[0].method).to.equal('POST'); + expect(requests[0].url).to.equal('https://hb.incrementxserv.com/vzhbidder/bid'); + const data = JSON.parse(decodeURI(requests[0].data.q)); + expect(data._vzPlacementId).to.equal('IX-HB-12346'); + expect(data.sizes).to.be.a('array'); + expect(data.mChannel).to.equal(2); }); }); - describe('interpretResponse', function () { - let serverResponse = { + const bannerServerResponse = { body: { - vzhPlacementId: 'PNX-HB-F796830VCF3C4B', - bid: 'BID-XXXX-XXXX', - adWidth: '300', - adHeight: '250', - cpm: '0.7', - ad: '

Ad from IncrementX

', - slotBidId: 'bid-id-123456', - adType: '1', - settings: '1,2', - nurl: 'htt://nurl.com', - statusText: 'Success' + slotBidId: '2faedf3e89d123', + cpm: 0.5, + adWidth: 300, + adHeight: 250, + ad: '
Banner Ad
', + mediaType: BANNER, + netRevenue: true, + currency: 'USD', + advertiserDomains: ['example.com'] } }; - - let expectedResponse = [{ - requestId: 'bid-id-123456', - cpm: '0.7', - currency: 'USD', - adType: '1', - settings: '1,2', - netRevenue: false, - width: '300', - height: '250', - creativeId: 0, - ttl: 300, - ad: '

Ad from IncrementX

', - meta: { - mediaType: 'banner', - advertiserDomains: [] + const videoServerResponse = { + body: { + slotBidId: '2faedf3e89d124', + cpm: 1.0, + adWidth: 640, + adHeight: 480, + ad: 'Test VAST', + mediaType: VIDEO, + netRevenue: true, + currency: 'USD', + rUrl: 'https://example.com/vast.xml', + advertiserDomains: ['example.com'] } - }]; - - it('should correctly interpret valid banner response', function () { - let result = spec.interpretResponse(serverResponse); - expect(result).to.deep.equal(expectedResponse); + }; + const bidderRequest = { + refererInfo: { + page: 'https://someurl.com' + }, + data: { + bidderRequestData: { + bids: [videoBidRequest] + } + } + }; + it('should handle banner response', function () { + const bidResponses = spec.interpretResponse(bannerServerResponse, bidderRequest); + expect(bidResponses).to.have.lengthOf(1); + const bid = bidResponses[0]; + expect(bid.requestId).to.equal('2faedf3e89d123'); + expect(bid.cpm).to.equal(0.5); + expect(bid.width).to.equal(300); + expect(bid.height).to.equal(250); + expect(bid.ad).to.equal('
Banner Ad
'); + expect(bid.mediaType).to.equal(BANNER); + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); + }); + it('should handle video response', function () { + const bidResponses = spec.interpretResponse(videoServerResponse, bidderRequest); + expect(bidResponses).to.have.lengthOf(1); + const bid = bidResponses[0]; + expect(bid.requestId).to.equal('2faedf3e89d124'); + expect(bid.cpm).to.equal(1.0); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.vastXml).to.equal('Test VAST'); + expect(bid.renderer).to.exist; + expect(bid.mediaType).to.equal(VIDEO); + expect(bid.meta.advertiserDomains).to.deep.equal(['example.com']); }); }); }); diff --git a/test/spec/modules/inmobiBidAdapter_spec.js b/test/spec/modules/inmobiBidAdapter_spec.js new file mode 100644 index 00000000000..a7074a2eed8 --- /dev/null +++ b/test/spec/modules/inmobiBidAdapter_spec.js @@ -0,0 +1,1965 @@ +import { expect } from 'chai'; +import { + spec, +} from 'modules/inmobiBidAdapter.js'; +import * as utils from 'src/utils.js'; +import * as ajax from 'src/ajax.js'; +import { BANNER, NATIVE, VIDEO } from '../../../src/mediaTypes.js'; +import { hook } from '../../../src/hook'; +import { config } from '../../../src/config.js'; +import { syncAddFPDToBidderRequest } from '../../helpers/fpd'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/consentManagementGpp.js'; +import 'modules/priceFloors.js'; +import sinon from 'sinon'; + +// constants +const GVLID = 333; +export const ADAPTER_VERSION = 1.0; +const BIDDER_CODE = 'inmobi'; +export const EVENT_ENDPOINT = 'https://sync.inmobi.com'; + +describe('The inmobi bidding adapter', function () { + let utilsMock, sandbox, ajaxStub, fetchStub; ; + + beforeEach(function () { + // mock objects + utilsMock = sinon.mock(utils); + sandbox = sinon.sandbox.create(); + ajaxStub = sandbox.stub(ajax, 'ajax'); + fetchStub = sinon.stub(global, 'fetch').resolves(new Response('OK')); + }); + + afterEach(function () { + utilsMock.restore(); + sandbox.restore(); + ajaxStub.restore(); + fetchStub.restore(); + }); + + describe('onBidWon', function () { + // existence test + it('onBidWon function should be defined', function () { + expect(spec.onBidWon).to.exist.and.to.be.a('function'); + }); + + it('It should invoke onBidWon, resolving the eventType and domain', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e772c1', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 100 + } + }; + spec.onBidWon(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onBidWon`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + const fetchArgs = fetchStub.getCall(0).args; + /* + index fetch parameter + 0 -> URL + 1 -> options (method, headers, body, etc.) + */ + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + credentials: 'include', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'text/plain', + }); + }); + + it('onBidWon should not be called when loggingPercentage is set to 0', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e772c1', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 0 + } + }; + spec.onBidWon(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onBidWon`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(0); + }); + + it('onBidWon should not be called if the bid data is null', function () { + // Call onBidWon with null data + spec.onBidWon(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onBidderError', function () { + it('onBidderError function should be defined', function () { + expect(spec.onBidderError).to.exist.and.to.be.a('function'); + }); + + it('onBidderError should not be called if the bid data is null', function () { + // Call onBidError with null data + spec.onBidderError(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + + it('onBidderError should be called with the eventType', function () { + const bid = { + error: 'error', // Assuming this will be a mock or reference to an actual XMLHttpRequest object + bidderRequest: { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidder: 'inmobi', + bidderRequestId: '15246a574e859f' + } + }; + spec.onBidderError(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onBidderError`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + const fetchArgs = fetchStub.getCall(0).args; + /* + index fetch parameter + 0 -> URL + 1 -> options (method, headers, body, etc.) + */ + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + credentials: 'include', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'text/plain', + }); + }); + }); + + describe('onAdRenderSucceeded', function () { + // existence test + it('onAdRenderSucceeded function should be defined', function () { + expect(spec.onAdRenderSucceeded).to.exist.and.to.be.a('function'); + }); + + it('should invoke onAdRenderSucceeded, resolving the eventType and domain', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e772c1', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 100 + } + }; + spec.onAdRenderSucceeded(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onAdRenderSucceeded`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + const fetchArgs = fetchStub.getCall(0).args; + /* + index fetch parameter + 0 -> URL + 1 -> options (method, headers, body, etc.) + */ + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + credentials: 'include', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'text/plain', + }); + }); + + it('onAdRenderSucceeded should not be called when loggingPercentage is 0', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e772c1', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 0 + } + }; + spec.onAdRenderSucceeded(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onAdRenderSucceeded`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(0); + }); + + it('onAdRenderSucceeded should not be called if the bid data is null', function () { + // Call onAdRenderSucceeded with null data + spec.onAdRenderSucceeded(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onTimeout', function () { + // existence test + it('onTimeout function should be defined', function () { + expect(spec.onTimeout).to.exist.and.to.be.a('function'); + }); + + it('should invoke onTimeout, resolving the eventType and domain', function () { + const bid = [ + { + bidder: 'inmobi', + bidId: '51ef8751f9aead1', + adUnitCode: 'div-gpt-ad-14605057481561-0', + timeout: 3000, + auctionId: '18fd8b8b0bd7517' + } + ]; + spec.onTimeout(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onTimeout`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + const fetchArgs = fetchStub.getCall(0).args; + /* + index fetch parameter + 0 -> URL + 1 -> options (method, headers, body, etc.) + */ + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + credentials: 'include', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'text/plain', + }); + }); + + it('onTimeout should not be called if the bid data is null', function () { + // Call onTimeout with null data + spec.onTimeout(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('onSetTargeting', function () { + // existence test + it('The onSetTargeting function should be defined', function () { + expect(spec.onSetTargeting).to.exist.and.to.be.a('function'); + }); + + it('should invoke onSetTargeting, resolving the eventType and domain', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e7721c', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 100 + } + }; + spec.onSetTargeting(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onSetTargeting`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(1); + const fetchArgs = fetchStub.getCall(0).args; + /* + index fetch parameter + 0 -> URL + 1 -> options (method, headers, body, etc.) + */ + expect(fetchArgs[0]).to.equal(expectedUrl); + const actualPayload = fetchArgs[1]?.body; + expect(actualPayload).to.equal(expectedPayload); + expect(fetchArgs[1]).to.deep.include({ + method: 'POST', + credentials: 'include', + keepalive: true, + }); + expect(fetchArgs[1]?.headers).to.deep.equal({ + 'Content-Type': 'text/plain', + }); + }); + + it('onSetTargeting should not be called when loggingPercentage is 0', function () { + const bid = { + bidder: 'inmobi', + width: 300, + height: 250, + adId: '330a22bdea4cac1', + mediaType: 'banner', + cpm: 0.28, + ad: 'inmobiAd', + requestId: '418b37f85e7721c', + adUnitCode: 'div-gpt-ad-1460505748561-01', + size: '350x250', + adserverTargeting: { + hb_bidder: 'prebid', + hb_adid: '330a22bdea4cac1', + hb_pb: '0.20', + hb_size: '350x250' + }, + meta: { + loggingPercentage: 0 + } + }; + spec.onSetTargeting(bid); + // expected url and payload + const expectedUrl = `${EVENT_ENDPOINT}/report/onSetTargeting`; + const expectedPayload = JSON.stringify({ + domain: location.hostname, + eventPayload: bid.meta + }); + // assert statements + expect(fetchStub.callCount).to.be.equal(0); + }); + + it('onSetTargeting should not be called if the bid data is null', function () { + // Call onSetTargeting with null data + spec.onSetTargeting(null); + // Assert that ajax was not called since bid data is null + expect(fetchStub.callCount).to.be.equal(0); + }); + }); + + describe('isBidRequestValid', function () { + it('should return false when an invalid bid is provided', function () { + const bid = { + bidder: 'inmobi', + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(false); + }); + + it('should return true when the bid contains a PLC', function () { + const bid = { + bidder: 'inmobi', + params: { + plc: '123a', + }, + }; + const isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equal(true); + }); + }); + + describe('buildRequests', function () { + before(() => { + hook.ready(); + }) + afterEach(function () { + config.resetConfig(); + }); + const bidderRequest = { + refererInfo: { + page: 'inmobi', + topmostLocation: 'inmobi' + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '333': 1 + }, + }, + apiVersion: 1, + }, + }; + + it('request should build with correct plc', function () { + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'inmobi', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].ext.bidder.plc).to.deep.equal('123'); + }); + + it('request should build with correct imp', function () { + const expectedMetric = { + url: 'https://inmobi.com' + } + const bidRequests = [{ + bidId: 'bidId', + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + ortb2Imp: { + instl: 1, + metric: expectedMetric, + ext: { + gpid: 'gpid_inmobi' + }, + rwdd: 1 + }, + params: { + plc: '123ai', + bidfloor: 4.66, + bidfloorcur: 'USD' + } + }]; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].id).to.deep.equal('bidId'); + expect(ortbRequest.imp[0].tagid).to.deep.equal('impId'); + expect(ortbRequest.imp[0].instl).to.equal(1); + expect(ortbRequest.imp[0].bidfloor).to.equal(4.66); + expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); + expect(ortbRequest.imp[0].metric).to.deep.equal(expectedMetric); + expect(ortbRequest.imp[0].secure).to.equal(0); + expect(ortbRequest.imp[0].ext.gpid).to.equal('gpid_inmobi'); + expect(ortbRequest.imp[0].rwdd).to.equal(1); + }); + + it('request should build with proper site data', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '1234a', + }, + }, + ]; + const ortb2 = { + site: { + name: 'raapchikgames.com', + domain: 'raapchikgames.com', + keywords: 'test1, test2', + cat: ['IAB2'], + pagecat: ['IAB3'], + sectioncat: ['IAB4'], + page: 'https://raapchikgames.com', + ref: 'inmobi.com', + privacypolicy: 1, + content: { + url: 'https://raapchikgames.com/games1' + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.site.domain).to.equal('raapchikgames.com'); + expect(ortbRequest.site.publisher.domain).to.equal('inmobi'); + expect(ortbRequest.site.page).to.equal('https://raapchikgames.com'); + expect(ortbRequest.site.name).to.equal('raapchikgames.com'); + expect(ortbRequest.site.keywords).to.equal('test1, test2'); + expect(ortbRequest.site.cat).to.deep.equal(['IAB2']); + expect(ortbRequest.site.pagecat).to.deep.equal(['IAB3']); + expect(ortbRequest.site.sectioncat).to.deep.equal(['IAB4']); + expect(ortbRequest.site.ref).to.equal('inmobi.com'); + expect(ortbRequest.site.privacypolicy).to.equal(1); + expect(ortbRequest.site.content.url).to.equal('https://raapchikgames.com/games1') + }); + + it('request should build with proper device data', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '1234a', + }, + }, + ]; + const ortb2 = { + device: { + dnt: 0, + ua: 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36', + ip: '195.199.250.144', + h: 919, + w: 1920, + language: 'hu', + lmt: 1, + js: 1, + connectiontype: 0, + hwv: '5S', + model: 'iphone', + mccmnc: '310-005', + geo: { + lat: 40.0964439, + lon: -75.3009142 + } + } + }; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.device.dnt).to.equal(0); + expect(ortbRequest.device.lmt).to.equal(1); + expect(ortbRequest.device.js).to.equal(1); + expect(ortbRequest.device.connectiontype).to.equal(0); + expect(ortbRequest.device.ua).to.equal('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/130.0.0.0 Safari/537.36'); + expect(ortbRequest.device.ip).to.equal('195.199.250.144'); + expect(ortbRequest.device.h).to.equal(919); + expect(ortbRequest.device.w).to.equal(1920); + expect(ortbRequest.device.language).to.deep.equal('hu'); + expect(ortbRequest.device.hwv).to.deep.equal('5S'); + expect(ortbRequest.device.model).to.deep.equal('iphone'); + expect(ortbRequest.device.mccmnc).to.deep.equal('310-005'); + expect(ortbRequest.device.geo.lat).to.deep.equal(40.0964439); + expect(ortbRequest.device.geo.lon).to.deep.equal(-75.3009142); + }); + + it('should properly build a request with source object', function () { + const expectedSchain = { id: 'prebid' }; + const ortb2 = { + source: { + pchain: 'inmobi', + schain: expectedSchain + } + }; + const bidRequests = [ + { + bidder: 'inmobi', + bidId: 'bidId', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '124', + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.source.ext.schain).to.deep.equal(expectedSchain); + expect(ortbRequest.source.pchain).to.equal('inmobi'); + }); + + it('should properly user object', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123' + } + }, + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + yob: 2002, + keyowrds: 'test test', + gender: 'M', + customdata: 'test no', + geo: { + lat: 40.0964439, + lon: -75.3009142 + }, + ext: { + eids: [ + { + source: 'inmobi.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ] + } + } + } + } + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); + const ortbRequest = request.data; + expect(ortbRequest.user.yob).to.deep.equal(2002); + expect(ortbRequest.user.keyowrds).to.deep.equal('test test'); + expect(ortbRequest.user.gender).to.deep.equal('M'); + expect(ortbRequest.user.customdata).to.deep.equal('test no'); + expect(ortbRequest.user.geo.lat).to.deep.equal(40.0964439); + expect(ortbRequest.user.geo.lon).to.deep.equal(-75.3009142); + expect(ortbRequest.user.ext.eids).to.deep.equal([ + { + source: 'inmobi.com', + uids: [{ + id: 'iid', + atype: 1 + }] + } + ]); + }); + + it('should properly build a request regs object', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '1234a', + }, + }, + ]; + const ortb2 = { + regs: { + coppa: 1, + gpp: 'gpp_consent_string', + gpp_sid: [0, 1, 2], + us_privacy: 'yes us privacy applied' + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest({ ...bidderRequest, ortb2 })).data; + expect(ortbRequest.regs.coppa).to.equal(1); + expect(ortbRequest.regs.ext.gpp).to.equal('gpp_consent_string'); + expect(ortbRequest.regs.ext.gpp_sid).to.deep.equal([0, 1, 2]); + expect(ortbRequest.regs.ext.us_privacy).to.deep.equal('yes us privacy applied'); + }); + + it('gdpr test', function () { + // using privacy params from global bidder Request + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '1234a', + }, + }, + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.ext.gdpr).to.deep.equal(1); + expect(ortbRequest.user.ext.consent).to.equal('consentDataString'); + }); + + it('should properly set tmax if available', function () { + // using tmax from global bidder Request + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'bid-1', + transactionId: 'trans-1', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.tmax).to.equal(bidderRequest.timeout); + }); + + it('should properly build a request with bcat field', function () { + const bcat = ['IAB1', 'IAB2']; + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123a', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bcat + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bcat).to.deep.equal(bcat); + }); + + it('should properly build a request with badv field', function () { + const badv = ['ford.com']; + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '12ea', + }, + }, + ]; + const bidderRequest = { + ortb2: { + badv + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.badv).to.deep.equal(badv); + }); + + it('should properly build a request with bapp field', function () { + const bapp = ['raapchik.com']; + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123a', + }, + }, + ]; + const bidderRequest = { + ortb2: { + bapp + } + }; + + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.bapp).to.deep.equal(bapp); + }); + + it('banner request test', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'inmobi', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 50]], + pos: 1, + topframe: 0, + } + }, + params: { + plc: '123' + }, + ortb2Imp: { + banner: { + api: [1, 2], + mimes: ['image/jpg', 'image/gif'] + } + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(50); + expect(ortbRequest.imp[0].banner.pos).to.equal(1); + expect(ortbRequest.imp[0].banner.topframe).to.equal(0); + expect(ortbRequest.imp[0].banner.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].banner.mimes).to.deep.equal(['image/jpg', 'image/gif']); + }); + + it('banner request test with sizes > 1', function () { + const bidderRequest = {}; + const bidRequests = [ + { + bidId: 'bidId', + bidder: 'inmobi', + adUnitCode: 'bid-12', + mediaTypes: { + banner: { + sizes: [[320, 50], [720, 50]] + } + }, + params: { + plc: '123' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].banner).not.to.be.null; + expect(ortbRequest.imp[0].banner.format[0].w).to.equal(320); + expect(ortbRequest.imp[0].banner.format[0].h).to.equal(50); + expect(ortbRequest.imp[0].banner.format[1].w).to.equal(720); + expect(ortbRequest.imp[0].banner.format[1].h).to.equal(50); + }); + + if (FEATURES.VIDEO) { + it('video request test', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'bid-123', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 360], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 360, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + params: { + plc: '123' + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + expect(ortbRequest.imp[0].video.mimes).to.deep.equal(['video/mp4', 'video/x-flv']); + expect(ortbRequest.imp[0].video.maxduration).to.equal(30); + expect(ortbRequest.imp[0].video.api).to.deep.equal([1, 2]); + expect(ortbRequest.imp[0].video.protocols).to.deep.equal([2, 3]); + expect(ortbRequest.imp[0].video.plcmt).to.equal(3); + expect(ortbRequest.imp[0].video.w).to.equal(640); + expect(ortbRequest.imp[0].video.h).to.equal(360); + expect(ortbRequest.imp[0].video.linearity).to.equal(1); + expect(ortbRequest.imp[0].video.skipmin).to.equal(30); + expect(ortbRequest.imp[0].video.skipafter).to.equal(30); + expect(ortbRequest.imp[0].video.minbitrate).to.equal(10000); + expect(ortbRequest.imp[0].video.maxbitrate).to.equal(48000); + expect(ortbRequest.imp[0].video.delivery).to.deep.equal([1, 2, 3]); + expect(ortbRequest.imp[0].video.pos).to.equal(1); + expect(ortbRequest.imp[0].video.playbackend).to.equal(1); + }); + } + + if (FEATURES.VIDEO) { + it('video request with player size > 1 ', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'bid-123', + sizes: [[640, 360], [480, 320]], + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [[640, 360], [480, 320]], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 360, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + params: { + plc: '123' + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.w).to.be.equal(640); + expect(ortbRequest.imp[0].video.h).to.be.equal(360); + }); + } + + if (FEATURES.VIDEO) { + it('video request test when skip is 0', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'bid-123', + sizes: [[640, 360]], + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 360], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 360, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + skip: 0, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + params: { + plc: '123' + }, + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.skip).to.equal(0); + }); + } + + if (FEATURES.NATIVE) { + it('native request test without assests', function () { + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + params: { + 'plc': '123a' + } + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + expect(ortbRequest.imp[0].native).to.be.undefined; + }); + } + + if (FEATURES.NATIVE) { + it('native request with assets', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }]; + const bidRequests = [ + { + mediaTypes: { + native: {} + }, + nativeOrtbRequest: { + assets: assets + }, + params: {} + }, + ]; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const ortbRequest = request.data; + + expect(ortbRequest.imp[0].native.request).to.not.be.null; + const nativeRequest = JSON.parse(ortbRequest.imp[0].native.request); + expect(nativeRequest).to.have.property('assets'); + expect(nativeRequest.assets).to.deep.equal(assets); + }); + } + + it('should properly build a request when coppa flag is true', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: true }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(1); + }); + + it('should properly build a request when coppa flag is false', function () { + const bidRequests = []; + const bidderRequest = {}; + config.setConfig({ coppa: false }); + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs.coppa).to.equal(0); + }); + + it('should properly build a request when coppa flag is not defined', function () { + const bidRequests = []; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.regs?.coppa).to.be.undefined; + }); + + it('build a banner request with bidFloor', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50], [720, 90]] + } + }, + params: { + plc: '123a', + bidfloor: 1, + bidfloorcur: 'USD' + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1); + expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + + it('build a banner request with getFloor', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123a' + }, + getFloor: inputParams => { + return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).equal(1.23); + expect(ortbRequest.imp[0].bidfloorcur).equal('USD'); + }); + + if (FEATURES.VIDEO) { + it('build a video request with bidFloor', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + video: { + playerSize: [[480, 320], [720, 480]] + } + }, + params: { + plc: '123a', + bidfloor: 1, + bidfloorcur: 'USD' + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).to.equal(1); + expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); + }); + } + + if (FEATURES.VIDEO) { + it('build a video request with getFloor', function () { + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + video: { + playerSize: [[480, 320], [720, 480]] + } + }, + params: { + plc: '123a' + }, + getFloor: inputParams => { + return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + } + } + ]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].bidfloor).to.equal(1.23); + expect(ortbRequest.imp[0].bidfloorcur).to.equal('USD'); + }); + } + + if (FEATURES.NATIVE && FEATURES.VIDEO) { + it('build a mutli format request with getFloor', function () { + const assets = [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }, + { + required: 1, + id: 2, + title: { + len: 140, + } + }, + { + required: 1, + id: 3, + data: { + type: 1, + } + }]; + const bidRequests = [ + { + bidder: 'inmobi', + adUnitCode: 'impId', + mediaTypes: { + banner: { + sizes: [[320, 50], [720, 90]] + }, + video: { + playerSize: [640, 480], + }, + native: { + + } + }, + nativeOrtbRequest: { + assets: assets + }, + params: { + plc: '12456' + }, + getFloor: inputParams => { + return { currency: 'USD', floor: 1.23, size: '*', mediaType: '*' }; + } + }, + ]; + const bidderRequest = {}; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp[0].banner).not.to.be.undefined; + expect(ortbRequest.imp[0].video).not.to.be.undefined; + expect(ortbRequest.imp[0].native.request).not.to.be.undefined; + expect(ortbRequest.imp[0].bidfloor).to.deep.equal(1.23); + expect(ortbRequest.imp[0].bidfloorcur).to.deep.equal('USD'); + }); + } + + if (FEATURES.VIDEO) { + it('build a multi imp request', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 360], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 360, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + } + }, + params: { + plc: '123' + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123', + } + }]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp).to.have.lengthOf(2); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + // banner test + expect(ortbRequest.imp[1].banner).not.to.be.undefined; + }); + } + + if (FEATURES.VIDEO) { + it('build a multi format request', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'inbanner', + playerSize: [640, 360], + mimes: ['video/mp4', 'video/x-flv'], + maxduration: 30, + api: [1, 2], + protocols: [2, 3], + plcmt: 3, + w: 640, + h: 360, + linearity: 1, + skipmin: 30, + skipafter: 30, + minbitrate: 10000, + maxbitrate: 48000, + delivery: [1, 2, 3], + pos: 1, + playbackend: 1, + adPodDurationSec: 30, + durationRangeSec: [1, 30], + skip: 1, + minduration: 5, + startdelay: 5, + playbackmethod: [1, 3], + placement: 2 + }, + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123' + }, + }]; + const ortbRequest = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)).data; + expect(ortbRequest.imp).to.have.lengthOf(1); + expect(ortbRequest.imp[0].video.skip).to.equal(1); + expect(ortbRequest.imp[0].video.minduration).to.equal(5); + expect(ortbRequest.imp[0].video.startdelay).to.equal(5); + expect(ortbRequest.imp[0].video.playbackmethod).to.deep.equal([1, 3]); + expect(ortbRequest.imp[0].video.placement).to.equal(2); + // banner test + expect(ortbRequest.imp[0].banner).not.to.be.undefined; + }); + } + }); + + describe('getUserSyncs', function () { + const syncEndPoint = 'https://sync.inmobi.com/prebidjs?'; + + it('should return empty if iframe disabled and pixelEnabled is false', function () { + const res = spec.getUserSyncs({}); + expect(res).to.deep.equal([]); + }); + + it('should return user sync if iframe enabled is true', function () { + const res = spec.getUserSyncs({ iframeEnabled: true, pixelEnabled: true }); + expect(res).to.deep.equal([{ type: 'iframe', url: syncEndPoint }]); + }); + + it('should return user sync if iframe enabled is true and gdpr not present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 and gdpr consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}gdpr=1&gdpr_consent=GDPR_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true and gdpr = 1 , gdpr consent present and usp_consent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT'); + expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT` }]); + }); + + it('should return user sync if iframe enabled is true usp_consent present and gppConsent present', function () { + const res = spec.getUserSyncs({ iframeEnabled: true }, {}, { gdprApplies: false, consentString: undefined }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] }); + expect(res).to.deep.equal([{ type: 'iframe', url: `${syncEndPoint}us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51` }]); + }); + + const responses = [{ + body: { + ext: { + prebidjs: { + urls: [ + { + url: 'https://sync.inmobi.com/prebidjs' + }, + { + url: 'https://eus.rubiconproject.com/usync.html?p=test' + } + ] + } + } + } + }]; + + it('should return urls from response when iframe enabled is false and pixel enabled', function () { + const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses); + expect(res).to.deep.equal([ + { type: 'image', url: 'https://sync.inmobi.com/prebidjs' }, + { type: 'image', url: 'https://eus.rubiconproject.com/usync.html?p=test' } + ]) + }); + + it('should return urls from response when iframe enabled is false and pixel enabled and empty responses', function () { + const responses = []; + const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses); + expect(res).to.deep.equal([ + { type: 'image', url: `${syncEndPoint}` } + ]) + }); + + it('should return urls from response when iframe enabled is false and pixel enabled and no response', function () { + const responses = undefined; + const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses); + expect(res).to.deep.equal([ + { type: 'image', url: `${syncEndPoint}` } + ]) + }); + + it('should return urls from response when iframe enabled is false and all consent parameters present', function () { + const res = spec.getUserSyncs({ iframeEnabled: false, pixelEnabled: true }, responses, { gdprApplies: true, consentString: 'GDPR_CONSENT' }, 'USP_CONSENT', { gppString: 'GPP_STRING', applicableSections: [32, 51] }); + expect(res).to.deep.equal([ + { type: 'image', url: 'https://sync.inmobi.com/prebidjs?gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51' }, + { type: 'image', url: 'https://eus.rubiconproject.com/usync.html?p=test&gdpr=1&gdpr_consent=GDPR_CONSENT&us_privacy=USP_CONSENT&gpp=GPP_STRING&gpp_sid=32%2C51' } + ]) + }); + }); + + describe('interpretResponse', function () { + const bidderRequest = { + refererInfo: { + page: 'https://raapchikgames.com', + topmostLocation: 'https://raapchikgames.com' + }, + timeout: 3000, + gdprConsent: { + gdprApplies: true, + consentString: 'consentDataString', + vendorData: { + vendorConsents: { + '333': 1 + }, + }, + apiVersion: 1, + }, + }; + function mockResponse(winningBidId, mediaType) { + return { + id: '95d08af8-2d50-4d75-a411-8ecd9224970e', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '20dd72ed-930f-1000-e56f-07c37a793f30', + impid: winningBidId, + price: 1.1645, + adomain: ['advertiserDomain.sandbox.inmobi.com'], + crid: '88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301', + w: 320, + h: 50, + adm: 'test-ad', + mtype: mediaType, + nurl: 'https://et-l.w.inmobi.com/c.asm/', + api: 3, + cat: [], + ext: { + loggingPercentage: 100, + prebid: { + meta: { + advertiserDomains: ['advertiserDomain.sandbox.inmobi.com'], + networkName: 'inmobi' + } + } + } + } + ] + } + ], + ext: { + prebidjs: { + urls: [ + { url: 'https://sync.inmobi.com/prebidjs' }, + { url: 'https://eus.rubiconproject.com/usync.html?p=test' } + ] + } + } + }; + }; + + function mockResponseNative(winningBidId, mediaType) { + return { + id: '95d08af8-2d50-4d75-a411-8ecd9224970e', + cur: 'USD', + seatbid: [ + { + bid: [ + { + id: '20dd72ed-930f-1000-e56f-07c37a793f30', + impid: winningBidId, + price: 1.1645, + adomain: ['advertiserDomain.sandbox.inmobi.com'], + crid: '88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301', + w: 320, + h: 50, + adm: '{"native":{"ver":"1.2","assets":[{"img":{"w":100,"h":100,"type":3,"url":"https://supply.inmobicdn.net/sandbox-prod-assets/Native_testAd.png"},"id":1,"required":1},{"id":2,"title":{"len":140,"text":"Native-Title-InMobi-Sandbox"},"required":1},{"data":{"type":1,"value":""},"id":3,"required":1},{"data":{"type":2,"value":"InMobi native test - Subtitle"},"id":4,"required":0},{"img":{"w":20,"h":20,"type":1,"url":"https://supply.inmobicdn.net/sandbox-prod-assets/inmobi-Logo-150x150.png"},"id":5,"required":0}],"link":{"clicktrackers":["https://c-eus.w.inmobi.com/"],"url":"https://www.inmobi.com"},"eventtrackers":[{"method":1,"event":1,"url":"https://et-eus.w.inmobi.com/"}]}}', + mtype: mediaType, + nurl: 'https://et-l.w.inmobi.com/c.asm/', + api: 3, + cat: [], + ext: { + loggingPercentage: 100, + prebid: { + meta: { + advertiserDomains: ['advertiserDomain.sandbox.inmobi.com'], + networkName: 'inmobi' + } + } + } + } + ] + } + ], + ext: { + prebidjs: { + urls: [ + { url: 'https://sync.inmobi.com/prebidjs' }, + { url: 'https://eus.rubiconproject.com/usync.html?p=test' } + ] + } + } + }; + }; + + it('returns an empty array when bid response is empty', function () { + const bidRequests = []; + const response = {}; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse(response, request); + expect(bids).to.have.lengthOf(0); + }); + + it('should return an empty array when there is no bid response', function () { + const bidRequests = []; + const response = { seatbid: [] }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(0); + }); + + it('return banner response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '124a', + } + }]; + const response = mockResponse('bidId', 1); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.length(1); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].mediaType).to.deep.equal('banner'); + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30'); + expect(bids[0].cpm).to.equal(1.1645); + expect(bids[0].width).to.equal(320); + expect(bids[0].height).to.equal(50); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.loggingPercentage).to.equal(100); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + }); + + it('bid response when banner wins among two ad units', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + plc: '123', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123', + } + }]; + const response = mockResponse('bidId2', 1); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].mediaType).to.deep.equal('banner'); + expect(bids[0].requestId).to.deep.equal('bidId2'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30'); + expect(bids[0].cpm).to.equal(1.1645); + expect(bids[0].width).to.equal(320); + expect(bids[0].height).to.equal(50); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.loggingPercentage).to.equal(100); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + }); + + if (FEATURES.VIDEO) { + it('return instream video response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + plc: '123a1', + }, + }]; + const response = mockResponse('bidId', 2); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30'); + expect(bids[0].cpm).to.equal(1.1645); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(320); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.loggingPercentage).to.equal(100); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/'); + expect(bids[0].vastXml).to.equal('test-ad'); + }); + } + + if (FEATURES.VIDEO) { + it('return video outstream response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'outstream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + plc: '123a1', + }, + }]; + const response = mockResponse('bidId', 2); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30'); + expect(bids[0].cpm).to.equal(1.1645); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(320); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.loggingPercentage).to.equal(100); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/'); + expect(bids[0].vastXml).to.equal('test-ad'); + }); + } + + if (FEATURES.VIDEO) { + it('bid response when video wins among two ad units', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + mediaTypes: { + video: { + context: 'instream', + mimes: ['video/mpeg'], + playerSize: [640, 320], + protocols: [5, 6], + maxduration: 30, + api: [1, 2] + } + }, + params: { + plc: '123', + }, + }, { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123', + } + }]; + const response = mockResponse('bidId', 2); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(VIDEO); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30'); + expect(bids[0].cpm).to.equal(1.1645); + expect(bids[0].playerWidth).to.equal(640); + expect(bids[0].playerHeight).to.equal(320); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.loggingPercentage).to.equal(100); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + expect(bids[0].vastUrl).to.equal('https://et-l.w.inmobi.com/c.asm/'); + expect(bids[0].vastXml).to.equal('test-ad'); + }); + } + + if (FEATURES.NATIVE) { + it('should correctly parse a native bid response', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + params: { + plc: '123', + }, + native: true, + bidder: 'inmobi', + mediaTypes: { + native: { + image: { + required: true, + sizes: [120, 60], + sendId: true, + sendTargetingKeys: false + } + } + } + }]; + const response = mockResponseNative('bidId', 4); + const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + // testing + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30') + expect(bids[0].cpm).to.deep.equal(1.1645); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].width).to.deep.equal(320); + expect(bids[0].height).to.deep.equal(50); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.deep.equal(expectedAdmNativeOrtb); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + }); + } + + if (FEATURES.NATIVE) { + it('should correctly parse a native bid response when there are two ad units', function () { + const bidRequests = [{ + adUnitCode: 'impId', + bidId: 'bidId', + params: { + plc: '123', + }, + native: true, + bidder: 'inmobi', + mediaTypes: { + native: { + image: { + required: true, + sizes: [120, 60], + sendId: true, + sendTargetingKeys: false + } + } + } + }, + { + adUnitCode: 'impId', + bidId: 'bidId2', + mediaTypes: { + banner: { + sizes: [[320, 50]] + } + }, + params: { + plc: '123', + } + }]; + const response = mockResponseNative('bidId', 4); + const expectedAdmNativeOrtb = JSON.parse(response.seatbid[0].bid[0].adm).native; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); + expect(bids).to.have.lengthOf(1); + expect(bids[0].mediaType).to.equal(NATIVE); + // testing + expect(bids[0].requestId).to.deep.equal('bidId'); + expect(bids[0].seatBidId).to.deep.equal('20dd72ed-930f-1000-e56f-07c37a793f30') + expect(bids[0].cpm).to.deep.equal(1.1645); + expect(bids[0].currency).to.deep.equal('USD'); + expect(bids[0].width).to.deep.equal(320); + expect(bids[0].height).to.deep.equal(50); + expect(bids[0].ad).to.equal(undefined); + expect(bids[0].native.ortb).not.to.be.null; + expect(bids[0].native.ortb).to.deep.equal(expectedAdmNativeOrtb); + expect(bids[0].creativeId).to.deep.equal('88b37efcd5f34d368e60317c706942a4ef6f6976eb394046958fd0e8a7b75301'); + expect(bids[0].meta.advertiserDomains[0]).to.deep.equal('advertiserDomain.sandbox.inmobi.com'); + expect(bids[0].meta.prebid.meta.networkName).to.deep.equal('inmobi'); + }); + } + }); +}); diff --git a/test/spec/modules/insticatorBidAdapter_spec.js b/test/spec/modules/insticatorBidAdapter_spec.js index 5e41cd6d7aa..158fdebeb76 100644 --- a/test/spec/modules/insticatorBidAdapter_spec.js +++ b/test/spec/modules/insticatorBidAdapter_spec.js @@ -175,25 +175,6 @@ describe('InsticatorBidAdapter', function () { })).to.be.true; }) - it('should return false if video placement is not a number', () => { - expect(spec.isBidRequestValid({ - ...bidRequest, - ...{ - mediaTypes: { - video: { - mimes: [ - 'video/mp4', - 'video/mpeg', - ], - w: 250, - h: 300, - placement: 'NaN', - }, - } - } - })).to.be.false; - }); - it('should return false if video plcmt is not a number', () => { expect(spec.isBidRequestValid({ ...bidRequest, @@ -224,7 +205,7 @@ describe('InsticatorBidAdapter', function () { 'video/mpeg', ], playerSize: [250, 300], - placement: 1, + plcmt: 1, }, } } @@ -293,7 +274,7 @@ describe('InsticatorBidAdapter', function () { 'video/mpeg', ], playerSize: [250, 300], - placement: 1, + plcmt: 1, }, } }, @@ -306,7 +287,7 @@ describe('InsticatorBidAdapter', function () { 'video/x-flv', 'video/webm', ], - placement: 2, + plcmt: 2, }, } })).to.be.true; @@ -408,7 +389,7 @@ describe('InsticatorBidAdapter', function () { 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.js).to.equal(1); expect(data.device.ext).to.be.an('object'); expect(data.device.ext.localStorage).to.equal(true); expect(data.device.ext.cookies).to.equal(false); @@ -458,6 +439,13 @@ describe('InsticatorBidAdapter', function () { insticator: { adUnitId: bidRequest.params.adUnitId, }, + prebid: { + bidder: { + insticator: { + adUnitId: bidRequest.params.adUnitId, + } + } + } } }]); expect(data.ext).to.be.an('object'); @@ -569,6 +557,100 @@ describe('InsticatorBidAdapter', function () { expect(data.imp[0].video.h).to.equal(480); }); + it('should have bidder bidfloor from the request', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + floor: 0.5, + }, + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(0.5); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should have bidder bidfloorcur from the request', function () { + const expectedFloor = 1.5; + const currency = 'USD'; + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + floor: 0.5, + currency: 'USD', + }, + } + tempBiddRequest.getFloor = () => ({ floor: expectedFloor, currency }) + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(1.5); + expect(data.imp[0].bidfloorcur).to.equal('USD'); + }); + + it('should have 1 floor for banner 300x250 and 1.5 for 300x600', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + }, + mediaTypes: { + banner: { + sizes: [[300, 250]], + format: [{ w: 300, h: 250 }] + }, + }, + } + tempBiddRequest.getFloor = (params) => { + return { floor: params.size[1] === 250 ? 1 : 1.5, currency: 'USD' } + } + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(1); + + tempBiddRequest.mediaTypes.banner.format = [ { w: 300, h: 600 }, + ]; + const request2 = spec.buildRequests([tempBiddRequest], bidderRequest); + const data2 = JSON.parse(request2[0].data); + expect(data2.imp[0].bidfloor).to.equal(1.5); + }); + + it('should have 4 floor for video 300x250 and 4.5 for 300x600', function () { + const tempBiddRequest = { + ...bidRequest, + params: { + ...bidRequest.params, + }, + mediaTypes: { + video: { + mimes: [ + 'video/mp4', + 'video/mpeg', + ], + w: 300, + h: 250, + placement: 2, + }, + }, + } + tempBiddRequest.getFloor = (params) => { + return { floor: params.size[1] === 250 ? 4 : 4.5, currency: 'USD' } + } + + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.imp[0].bidfloor).to.equal(4); + + tempBiddRequest.mediaTypes.video.w = 300; + tempBiddRequest.mediaTypes.video.h = 600; + const request2 = spec.buildRequests([tempBiddRequest], bidderRequest); + const data2 = JSON.parse(request2[0].data); + expect(data2.imp[0].bidfloor).to.equal(4.5); + }); + it('should have sites first party data if present in bidderRequest ortb2', function () { bidderRequest = { ...bidderRequest, @@ -691,6 +773,37 @@ describe('InsticatorBidAdapter', function () { expect(data.regs.ext).to.have.property('us_privacy'); expect(data.regs.ext).to.have.property('gppSid'); }); + + it('should return true if publisherId is absent', () => { + expect(spec.isBidRequestValid(bidRequest)).to.be.true; + }) + + it('should have publisher object with id in site object, if publisherId present in params', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '86dd03a1-053f-4e3e-90e7-389070a0c62c' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.publisher).to.be.an('object'); + expect(data.site.publisher.id).to.equal(tempBiddRequest.params.publisherId) + }); + + it('should have publisher object should be empty, if publisherId is empty string', function () { + const tempBiddRequest = { + ...bidRequest, + } + tempBiddRequest.params = { + ...tempBiddRequest.params, + publisherId: '' + } + const requests = spec.buildRequests([tempBiddRequest], bidderRequest); + const data = JSON.parse(requests[0].data); + expect(data.site.publisher).to.not.an('object'); + }); }); describe('interpretResponse', function () { diff --git a/test/spec/modules/intentIqAnalyticsAdapter_spec.js b/test/spec/modules/intentIqAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..5ba0686af0b --- /dev/null +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -0,0 +1,327 @@ +import { expect } from 'chai'; +import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js'; +import * as utils from 'src/utils.js'; +import { server } from 'test/mocks/xhr.js'; +import { config } from 'src/config.js'; +import { EVENTS } from 'src/constants.js'; +import * as events from 'src/events.js'; +import { getStorageManager } from 'src/storageManager.js'; +import sinon from 'sinon'; +import { REPORTER_ID, preparePayload } from '../../../modules/intentIqAnalyticsAdapter'; +import {FIRST_PARTY_KEY, VERSION} from '../../../libraries/intentIqConstants/intentIqConstants.js'; +import * as detectBrowserUtils from '../../../libraries/intentIqUtils/detectBrowserUtils.js'; +import {getReferrer, appendVrrefAndFui} from '../../../libraries/intentIqUtils/getRefferer.js'; + +const partner = 10; +const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; +const version = VERSION; + +const storage = getStorageManager({ moduleType: 'analytics', moduleName: 'iiqAnalytics' }); + +const USERID_CONFIG = [ + { + 'name': 'intentIqId', + 'params': { + 'partner': partner, + 'unpack': null, + }, + 'storage': { + 'type': 'html5', + 'name': 'intentIqId', + 'expires': 60, + 'refreshInSeconds': 14400 + } + } +]; + +let wonRequest = { + 'bidderCode': 'pubmatic', + 'width': 728, + 'height': 90, + 'statusMessage': 'Bid available', + 'adId': '23caeb34c55da51', + 'requestId': '87615b45ca4973', + 'transactionId': '5e69fd76-8c86-496a-85ce-41ae55787a50', + 'auctionId': '0cbd3a43-ff45-47b8-b002-16d3946b23bf', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 5, + 'currency': 'USD', + 'ttl': 300, + 'referrer': '', + 'adapterCode': 'pubmatic', + 'originalCpm': 5, + 'originalCurrency': 'USD', + 'responseTimestamp': 1669644710345, + 'requestTimestamp': 1669644710109, + 'bidder': 'testbidder', + 'adUnitCode': 'addUnitCode', + 'timeToRespond': 236, + 'pbLg': '5.00', + 'pbMg': '5.00', + 'pbHg': '5.00', + 'pbAg': '5.00', + 'pbDg': '5.00', + 'pbCg': '', + 'size': '728x90', + 'status': 'rendered' +}; + +describe('IntentIQ tests all', function () { + let logErrorStub; + let getWindowSelfStub; + let getWindowTopStub; + let getWindowLocationStub; + let detectBrowserStub; + + beforeEach(function () { + logErrorStub = sinon.stub(utils, 'logError'); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG); + sinon.stub(events, 'getEvents').returns([]); + iiqAnalyticsAnalyticsAdapter.enableAnalytics({ + provider: 'iiqAnalytics', + }); + iiqAnalyticsAnalyticsAdapter.initOptions = { + lsValueInitialized: false, + partner: null, + fpid: null, + userGroup: null, + currentGroup: null, + dataInLs: null, + eidl: null, + lsIdsInitialized: false, + manualWinReportEnabled: false, + domainName: null + }; + if (iiqAnalyticsAnalyticsAdapter.track.restore) { + iiqAnalyticsAnalyticsAdapter.track.restore(); + } + sinon.spy(iiqAnalyticsAnalyticsAdapter, 'track'); + }); + + afterEach(function () { + logErrorStub.restore(); + if (getWindowSelfStub) getWindowSelfStub.restore(); + if (getWindowTopStub) getWindowTopStub.restore(); + if (getWindowLocationStub) getWindowLocationStub.restore(); + if (detectBrowserStub) detectBrowserStub.restore(); + config.getConfig.restore(); + events.getEvents.restore(); + iiqAnalyticsAnalyticsAdapter.disableAnalytics(); + if (iiqAnalyticsAnalyticsAdapter.track.restore) { + iiqAnalyticsAnalyticsAdapter.track.restore(); + } + localStorage.clear(); + server.reset(); + }); + + it('IIQ Analytical Adapter bid win report', function () { + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({href: 'http://localhost:9876/'}); + const expectedVrref = encodeURIComponent(getWindowLocationStub().href); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); + expect(request.url).to.contain(`&jsver=${version}`); + expect(request.url).to.contain(`&vrref=${expectedVrref}`); + expect(request.url).to.contain('&payload='); + expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + }); + + it('should initialize with default configurations', function () { + expect(iiqAnalyticsAnalyticsAdapter.initOptions.lsValueInitialized).to.be.false; + }); + + it('should handle BID_WON event with group configuration from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + const expectedVrref = encodeURIComponent('http://localhost:9876/'); + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain('https://reports.intentiq.com/report?pid=' + partner + '&mct=1'); + expect(request.url).to.contain(`&jsver=${version}`); + expect(request.url).to.contain(`&vrref=${expectedVrref}`); + expect(request.url).to.contain('iiqid=testpcid'); + }); + + it('should handle BID_WON event with default group configuration', function () { + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + const defaultDataObj = JSON.parse(defaultData) + + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + const dataToSend = preparePayload(wonRequest); + const base64String = btoa(JSON.stringify(dataToSend)); + const payload = `[%22${base64String}%22]`; + const expectedUrl = appendVrrefAndFui( + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&source=pbjs&payload=${payload}&uh=`, + iiqAnalyticsAnalyticsAdapter.initOptions.domainName + ); + expect(request.url).to.equal(expectedUrl); + expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) + }); + + it('should not send request if manualWinReportEnabled is true', function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; + events.emit(EVENTS.BID_WON, wonRequest); + expect(server.requests.length).to.equal(1); + }); + + it('should read data from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"group": "A"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid", "eidl": 10}'); + events.emit(EVENTS.BID_WON, wonRequest); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs).to.equal('testpcid'); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.eidl).to.equal(10); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('A'); + }); + + it('should handle initialization values from local storage', function () { + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); + events.emit(EVENTS.BID_WON, wonRequest); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup).to.equal('B'); + expect(iiqAnalyticsAnalyticsAdapter.initOptions.fpid).to.be.not.null; + }); + + it('should handle reportExternalWin', function () { + events.emit(EVENTS.BID_REQUESTED); + iiqAnalyticsAnalyticsAdapter.initOptions.manualWinReportEnabled = true; + localStorage.setItem(FIRST_PARTY_KEY, '{"pcid":"testpcid", "group": "B"}'); + localStorage.setItem(FIRST_PARTY_KEY + '_' + partner, '{"data":"testpcid"}'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin).to.be.a('function'); + expect(window[`intentIqAnalyticsAdapter_${partner}`].reportExternalWin({cpm: 1, currency: 'USD'})).to.equal(false); + }); + + it('should return window.location.href when window.self === window.top', function () { + // Stub helper functions + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(window); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns(window); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns({ href: 'http://localhost:9876/' }); + + const referrer = getReferrer(); + expect(referrer).to.equal(encodeURIComponent('http://localhost:9876/')); + }); + + it('should return window.top.location.href when window.self !== window.top and access is successful', function () { + // Stub helper functions to simulate iframe + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns({}); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns({ location: { href: 'http://example.com/' } }); + + const referrer = getReferrer(); + + expect(referrer).to.equal(encodeURIComponent('http://example.com/')); + }); + + it('should return an empty string and log an error when accessing window.top.location.href throws an error', function () { + // Stub helper functions to simulate error + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns({}); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').throws(new Error('Access denied')); + + const referrer = getReferrer(); + expect(referrer).to.equal(''); + expect(logErrorStub.calledOnce).to.be.true; + expect(logErrorStub.firstCall.args[0]).to.contain('Error accessing location: Error: Access denied'); + }); + + it('should not send request if the browser is in blacklist (chrome)', function () { + const USERID_CONFIG_BROWSER = [...USERID_CONFIG]; + USERID_CONFIG_BROWSER[0].params.browserBlackList = 'ChrOmE'; + + config.getConfig.restore(); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); + detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('chrome'); + + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.equal(0); + }); + + it('should send request if the browser is not in blacklist (safari)', function () { + const USERID_CONFIG_BROWSER = [...USERID_CONFIG]; + USERID_CONFIG_BROWSER[0].params.browserBlackList = 'chrome,firefox'; + + config.getConfig.restore(); + sinon.stub(config, 'getConfig').withArgs('userSync.userIds').returns(USERID_CONFIG_BROWSER); + detectBrowserStub = sinon.stub(detectBrowserUtils, 'detectBrowser').returns('safari'); + + localStorage.setItem(FIRST_PARTY_KEY, defaultData); + events.emit(EVENTS.BID_WON, wonRequest); + + expect(server.requests.length).to.be.above(0); + const request = server.requests[0]; + expect(request.url).to.contain(`https://reports.intentiq.com/report?pid=${partner}&mct=1`); + expect(request.url).to.contain(`&jsver=${version}`); + expect(request.url).to.contain(`&vrref=${encodeURIComponent('http://localhost:9876/')}`); + expect(request.url).to.contain('&payload='); + expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + }); + + const testCasesVrref = [ + { + description: 'domainName matches window.top.location.href', + getWindowSelf: {}, + getWindowTop: { location: { href: 'http://example.com/page' } }, + getWindowLocation: { href: 'http://example.com/page' }, + domainName: 'example.com', + expectedVrref: encodeURIComponent('http://example.com/page'), + shouldContainFui: false + }, + { + description: 'domainName does not match window.top.location.href', + getWindowSelf: {}, + getWindowTop: { location: { href: 'http://anotherdomain.com/page' } }, + getWindowLocation: { href: 'http://anotherdomain.com/page' }, + domainName: 'example.com', + expectedVrref: encodeURIComponent('example.com'), + shouldContainFui: false + }, + { + description: 'domainName is missing, only fui=1 is returned', + getWindowSelf: {}, + getWindowTop: { location: { href: '' } }, + getWindowLocation: { href: '' }, + domainName: null, + expectedVrref: '', + shouldContainFui: true + }, + { + description: 'domainName is missing', + getWindowSelf: {}, + getWindowTop: { location: { href: 'http://example.com/page' } }, + getWindowLocation: { href: 'http://example.com/page' }, + domainName: null, + expectedVrref: encodeURIComponent('http://example.com/page'), + shouldContainFui: false + }, + ]; + + testCasesVrref.forEach(({ description, getWindowSelf, getWindowTop, getWindowLocation, domainName, expectedVrref, shouldContainFui }) => { + it(`should append correct vrref when ${description}`, function () { + getWindowSelfStub = sinon.stub(utils, 'getWindowSelf').returns(getWindowSelf); + getWindowTopStub = sinon.stub(utils, 'getWindowTop').returns(getWindowTop); + getWindowLocationStub = sinon.stub(utils, 'getWindowLocation').returns(getWindowLocation); + + const url = 'https://reports.intentiq.com/report?pid=10'; + const modifiedUrl = appendVrrefAndFui(url, domainName); + const urlObj = new URL(modifiedUrl); + + const vrref = encodeURIComponent(urlObj.searchParams.get('vrref') || ''); + const fui = urlObj.searchParams.get('fui'); + + expect(vrref).to.equal(expectedVrref); + expect(urlObj.searchParams.has('fui')).to.equal(shouldContainFui); + if (shouldContainFui) { + expect(fui).to.equal('1'); + } + }); + }); +}); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index ef174af416b..b95c3603e3a 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,30 +2,64 @@ import { expect } from 'chai'; import { intentIqIdSubmodule, storage } from 'modules/intentIqIdSystem.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; +import { decryptData, handleClientHints, readData } from '../../../modules/intentIqIdSystem'; +import {getGppValue} from '../../../libraries/intentIqUtils/getGppValue.js'; +import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; +import { clearAllCookies } from '../../helpers/cookies'; +import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/intentIqUtils/detectBrowserUtils'; +import {CLIENT_HINTS_KEY, FIRST_PARTY_KEY} from '../../../libraries/intentIqConstants/intentIqConstants.js'; const partner = 10; const pai = '11'; const pcid = '12'; -const enableCookieStorage = true; const defaultConfigParams = { params: { partner: partner } }; const paiConfigParams = { params: { partner: partner, pai: pai } }; const pcidConfigParams = { params: { partner: partner, pcid: pcid } }; -const enableCookieConfigParams = { params: { partner: partner, enableCookieStorage: enableCookieStorage } }; -const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid, enableCookieStorage: enableCookieStorage } }; +const allConfigParams = { params: { partner: partner, pai: pai, pcid: pcid } }; const responseHeader = { 'Content-Type': 'application/json' } +export const testClientHints = { + architecture: 'x86', + bitness: '64', + brands: [ + { + brand: 'Not(A:Brand', + version: '24' + }, + { + brand: 'Chromium', + version: '122' + } + ], + fullVersionList: [ + { + brand: 'Not(A:Brand', + version: '24.0.0.0' + }, + { + brand: 'Chromium', + version: '122.0.6261.128' + } + ], + mobile: false, + model: '', + platform: 'Linux', + platformVersion: '6.5.0', + wow64: false +}; + describe('IntentIQ tests', function () { let logErrorStub; let testLSValue = { - 'date': 1651945280759, + 'date': Date.now(), 'cttl': 2000, 'rrtt': 123 } let testLSValueWithData = { - 'date': 1651945280759, + 'date': Date.now(), 'cttl': 9999999999999, 'rrtt': 123, - 'data': 'previousTestData' + 'data': 'U2FsdGVkX185JJuQ2Zk0JLGjpgEbqxNy0Yl2qMtj9PqA5Q3IkNQYyTyFyTOkJi9Nf7E43PZQvIUgiUY/A9QxKYmy1LHX9LmZMKlLOcY1Je13Kr1EN7HRF8nIIWXo2jRgS5n0Nmty5995x3YMjLw+aRweoEtcrMC6p4wOdJnxfrOhdg0d/R7b8C+IN85rDLfNXANL1ezX8zwh4rj9XpMmWw==' } let testResponseWithValues = { 'abPercentage': 90, @@ -39,11 +73,17 @@ describe('IntentIQ tests', function () { } beforeEach(function () { + localStorage.clear(); + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(FIRST_PARTY_KEY, '', expiredDate, 'Lax'); + storage.setCookie(FIRST_PARTY_KEY + '_' + partner, '', expiredDate, 'Lax'); logErrorStub = sinon.stub(utils, 'logError'); }); afterEach(function () { logErrorStub.restore(); + clearAllCookies(); + localStorage.clear(); }); it('should log an error if no configParams were passed when getId', function () { @@ -64,7 +104,7 @@ describe('IntentIQ tests', function () { expect(submodule).to.be.undefined; }); - it('should not save data in cookie if enableCookieStorage configParam not set', function () { + it('should not save data in cookie if relevant type not set', function () { let callBackSpy = sinon.spy(); let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); @@ -79,9 +119,9 @@ describe('IntentIQ tests', function () { expect(storage.getCookie('_iiq_fdata_' + partner)).to.equal(null); }); - it('should save data in cookie if enableCookieStorage configParam set to true', function () { + it('should save data in cookie if storage type is "cookie"', function () { let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + let submoduleCallback = intentIqIdSubmodule.getId({ ...allConfigParams, enabledStorageTypes: ['cookie'] }).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='); @@ -91,9 +131,10 @@ describe('IntentIQ tests', function () { JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) ); expect(callBackSpy.calledOnce).to.be.true; - const cookieValue = storage.getCookie('_iiq_fdata_' + partner) - expect(cookieValue).to.not.equal(null) - expect(JSON.parse(cookieValue).data).to.be.equal('test_personid'); + const cookieValue = storage.getCookie('_iiq_fdata_' + partner); + expect(cookieValue).to.not.equal(null); + const decryptedData = JSON.parse(decryptData(JSON.parse(cookieValue).data)); + expect(decryptedData).to.deep.equal({eids: ['test_personid']}); }); it('should call the IntentIQ endpoint with only partner', function () { @@ -111,8 +152,6 @@ describe('IntentIQ tests', function () { }); 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); @@ -199,7 +238,7 @@ describe('IntentIQ tests', function () { 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'); + expect(callBackSpy.args[0][0]).to.deep.equal(['test_personid']); }); it('dont save result if ls=false', function () { @@ -214,22 +253,7 @@ describe('IntentIQ tests', function () { 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'); + expect(callBackSpy.args[0][0]).to.deep.equal({eids: []}); }); it('send addition parameters if were found in localstorage', function () { @@ -248,16 +272,225 @@ describe('IntentIQ tests', function () { JSON.stringify(testResponseWithValues) ); expect(callBackSpy.calledOnce).to.be.true; - expect(callBackSpy.args[0][0]).to.be.eq(testResponseWithValues.data); + expect(callBackSpy.args[0][0]).to.deep.equal([testResponseWithValues.data]); }); it('return data stored in local storage ', function () { - localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)) + localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValueWithData)); + let returnedValue = intentIqIdSubmodule.getId(allConfigParams); + expect(returnedValue.id).to.deep.equal(JSON.parse(decryptData(testLSValueWithData.data)).eids); + }); + + it('should handle browser blacklisting', function () { + let configParamsWithBlacklist = { + params: { partner: partner, browserBlackList: 'chrome' } + }; + sinon.stub(navigator, 'userAgent').value('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'); + let submoduleCallback = intentIqIdSubmodule.getId(configParamsWithBlacklist); + expect(logErrorStub.calledOnce).to.be.true; + expect(submoduleCallback).to.be.undefined; + }); + + it('should handle invalid JSON in readData', function () { + localStorage.setItem('_iiq_fdata_' + partner, 'invalid_json'); let callBackSpy = sinon.spy(); - let submoduleCallback = intentIqIdSubmodule.getId(allConfigParams).callback; + let submoduleCallback = intentIqIdSubmodule.getId(defaultConfigParams).callback; submoduleCallback(callBackSpy); - expect(server.requests.length).to.be.equal(0); + 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; - expect(callBackSpy.args[0][0]).to.be.equal(testLSValueWithData.data); + expect(logErrorStub.called).to.be.true; + }); + + describe('getGppValue', function () { + const testCases = [ + { + description: 'should return gppString and gpi=0 when GPP data exists', + input: { gppString: '{"key1":"value1","key2":"value2"}' }, + expectedOutput: { gppString: '{"key1":"value1","key2":"value2"}', gpi: 0 } + }, + { + description: 'should return empty gppString and gpi=1 when GPP data does not exist', + input: null, + expectedOutput: { gppString: '', gpi: 1 } + }, + { + description: 'should return empty gppString and gpi=1 when gppString is not set', + input: {}, + expectedOutput: { gppString: '', gpi: 1 } + }, + { + description: 'should handle GPP data with empty string', + input: { gppString: '' }, + expectedOutput: { gppString: '', gpi: 1 } + } + ]; + + testCases.forEach(({ description, input, expectedOutput }) => { + it(description, function () { + sinon.stub(gppDataHandler, 'getConsentData').returns(input); + + const result = getGppValue(); + expect(result).to.deep.equal(expectedOutput); + + gppDataHandler.getConsentData.restore(); + }); + }); + }); + + describe('detectBrowserFromUserAgent', function () { + it('should detect Chrome browser', function () { + const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('chrome'); + }); + + it('should detect Safari browser', function () { + const userAgent = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0.3 Safari/605.1.15'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('safari'); + }); + + it('should detect Firefox browser', function () { + const userAgent = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:89.0) Gecko/20100101 Firefox/89.0'; + const result = detectBrowserFromUserAgent(userAgent); + expect(result).to.equal('firefox'); + }); + }); + + describe('detectBrowserFromUserAgentData', function () { + it('should detect Microsoft Edge browser', function () { + const userAgentData = { + brands: [ + { brand: 'Microsoft Edge', version: '91' }, + { brand: 'Chromium', version: '91' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('edge'); + }); + + it('should detect Chrome browser', function () { + const userAgentData = { + brands: [ + { brand: 'Google Chrome', version: '91' }, + { brand: 'Chromium', version: '91' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('chrome'); + }); + + it('should return unknown for unrecognized user agent data', function () { + const userAgentData = { + brands: [ + { brand: 'Unknown Browser', version: '1.0' } + ] + }; + const result = detectBrowserFromUserAgentData(userAgentData); + expect(result).to.equal('unknown'); + }); + }); + + describe('IntentIQ consent management within getId', function () { + let uspDataHandlerStub; + let gppDataHandlerStub; + + beforeEach(function () { + localStorage.clear(); + const expiredDate = new Date(0).toUTCString(); + storage.setCookie(FIRST_PARTY_KEY, '', expiredDate, 'Lax'); + storage.setCookie(FIRST_PARTY_KEY + '_' + partner, '', expiredDate, 'Lax'); + uspDataHandlerStub = sinon.stub(uspDataHandler, 'getConsentData'); + gppDataHandlerStub = sinon.stub(gppDataHandler, 'getConsentData'); + }); + + afterEach(function () { + uspDataHandlerStub.restore(); + gppDataHandlerStub.restore(); + }); + + it('should set cmpData.us_privacy if uspData exists', function () { + const uspData = '1NYN'; + uspDataHandlerStub.returns(uspData); + 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({ pid: 'test_pid', data: 'test_personid', ls: false, isOptedOut: false }) + ); + expect(callBackSpy.calledOnce).to.be.true; + + // Check the local storage directly to see if cmpData.us_privacy was set correctly + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.uspapi_value).to.equal(uspData); + }); + + it('should create a request with gpp data if gppData exists and has gppString', function () { + const mockGppValue = { + gppString: '{"key1":"value1","key2":"value2"}', + gpi: 0 + }; + + const mockConfig = { + params: { partner: partner }, + enabledStorageTypes: ['localStorage'] + }; + + gppDataHandlerStub.returns(mockGppValue); + + let callBackSpy = sinon.spy(); + let submoduleCallback = intentIqIdSubmodule.getId(mockConfig).callback; + submoduleCallback(callBackSpy); + + let request = server.requests[0]; + + request.respond( + 200, + responseHeader, + JSON.stringify({ pid: 'test_pid', data: 'test_personid', ls: true }) + ); + + expect(request.url).to.contain(`&gpp=${encodeURIComponent(mockGppValue.gppString)}`); + expect(callBackSpy.calledOnce).to.be.true; + + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.gpp_string_value).to.equal(mockGppValue.gppString); + }); + }); + + it('should get and save client hints to storge', async () => { + // Client hints are async function, thats why async/await is using + localStorage.clear(); + Object.defineProperty(navigator, 'userAgentData', { + value: { getHighEntropyValues: async () => testClientHints }, + configurable: true + }); + await intentIqIdSubmodule.getId(defaultConfigParams); + const savedClientHints = readData(CLIENT_HINTS_KEY, ['html5']); + expect(savedClientHints).to.equal(handleClientHints(testClientHints)); + }); + + it('should run callback from params', async () => { + let wasCallbackCalled = false + const callbackConfigParams = { params: { partner: partner, + pai: pai, + pcid: pcid, + browserBlackList: 'Chrome', + callback: () => { + wasCallbackCalled = true + } } }; + + await intentIqIdSubmodule.getId(callbackConfigParams); + expect(wasCallbackCalled).to.equal(true); }); }); diff --git a/test/spec/modules/invibesBidAdapter_spec.js b/test/spec/modules/invibesBidAdapter_spec.js index ba2d8f5255a..e12376b2a37 100644 --- a/test/spec/modules/invibesBidAdapter_spec.js +++ b/test/spec/modules/invibesBidAdapter_spec.js @@ -1,6 +1,6 @@ import {expect} from 'chai'; import { config } from 'src/config.js'; -import {spec, resetInvibes, stubDomainOptions, readGdprConsent} from 'modules/invibesBidAdapter.js'; +import {spec, resetInvibes, stubDomainOptions, readGdprConsent, storage} from 'modules/invibesBidAdapter.js'; describe('invibesBidAdapter:', function () { const BIDDER_CODE = 'invibes'; @@ -187,6 +187,8 @@ describe('invibesBidAdapter:', function () { }; } + let sandbox; + beforeEach(function () { resetInvibes(); $$PREBID_GLOBAL$$.bidderSettings = { @@ -196,11 +198,13 @@ describe('invibesBidAdapter:', function () { }; document.cookie = ''; this.cStub1 = sinon.stub(console, 'info'); + sandbox = sinon.sandbox.create(); }); afterEach(function () { $$PREBID_GLOBAL$$.bidderSettings = {}; this.cStub1.restore(); + sandbox.restore(); }); describe('isBidRequestValid:', function () { @@ -528,6 +532,8 @@ describe('invibesBidAdapter:', function () { }); it('sends undefined lid when no cookie', function () { + sandbox.stub(storage, 'getDataFromLocalStorage').returns(null); + sandbox.stub(storage, 'getCookie').returns(null); let request = spec.buildRequests(bidRequests, bidderRequestWithPageInfo); expect(request.data.lId).to.be.undefined; }); @@ -563,7 +569,7 @@ describe('invibesBidAdapter:', function () { it('does not send handIid when it doesnt exist in cookie', function () { top.window.invibes.optIn = 1; top.window.invibes.purposes = [true, false, false, false, false, false, false, false, false, false]; - global.document.cookie = ''; + sandbox.stub(storage, 'getCookie').returns(null) let bidderRequest = { gdprConsent: { vendorData: { @@ -1505,5 +1511,28 @@ describe('invibesBidAdapter:', function () { let response = spec.getUserSyncs({iframeEnabled: false}); expect(response).to.equal(undefined); }); + + it('uses uspConsent when no gdprConsent', function () { + let bidderRequest = { + uspConsent: '1YNY', + refererInfo: { + page: 'https://randomWeb.com?someFakePara=fakeValue&secondParam=secondValue' + } + }; + + let request = spec.buildRequests(bidRequests, bidderRequest); + expect(top.window.invibes.optIn).to.equal(2); + expect(top.window.invibes.GdprModuleInstalled).to.be.false; + expect(top.window.invibes.UspModuleInstalled).to.be.true; + var index; + for (index = 0; index < top.window.invibes.purposes.length; ++index) { + expect(top.window.invibes.purposes[index]).to.be.true; + } + for (index = 0; index < top.window.invibes.legitimateInterests.length; ++index) { + expect(top.window.invibes.legitimateInterests[index]).to.be.true; + } + expect(request.data.tc).to.not.exist; + expect(request.data.uspc).to.equal(bidderRequest.uspConsent); + }); }); }); diff --git a/test/spec/modules/iqmBidAdapter_spec.js b/test/spec/modules/iqmBidAdapter_spec.js deleted file mode 100644 index 2f8b5811b2f..00000000000 --- a/test/spec/modules/iqmBidAdapter_spec.js +++ /dev/null @@ -1,414 +0,0 @@ -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'}, - ortb2Imp: {ext: {data: {'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'}, - ortb2Imp: {ext: {data: {'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: { - page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', - domain: 'test.localhost.com:9999', - ref: null, - 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'}, - ortb2Imp: {ext: {data: {'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: { - page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', - domain: 'test.localhost.com:9999', - ref: null, - 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 () { - // TODO: this is wrong on multiple levels - // The payload contains everything in `bidderRequest`; that is sometimes not even serializable - // this should not be testing the validity of internal Prebid structures - const request = spec.buildRequests(validBidRequests, bidderRequest); - const payload = request[0].data; - - expect(payload.bidderRequest.refererInfo).to.exist; - }); - }) - - 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'}, - ortb2Imp: {ext: {data: {'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'}, - ortb2Imp: {ext: {data: {'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: { - page: 'http://test.localhost:9999/integrationExamples/gpt/hello_world.html', - domain: 'test.localhost.com:9999', - ref: null, - 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'}, - ortb2Imp: {ext: {data: {'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: { - page: 'http://test.localhost:9999/integrationExamples/gpt/pbjs_video_adUnit.html', - domain: 'test.localhost.com:9999', - ref: '', - 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/iqxBidAdapter_spec.js b/test/spec/modules/iqxBidAdapter_spec.js index f5e680c8e0b..29b03a4ed3a 100644 --- a/test/spec/modules/iqxBidAdapter_spec.js +++ b/test/spec/modules/iqxBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {spec, getBidFloor} from 'modules/iqxBidAdapter.js'; +import {spec} from 'modules/iqxBidAdapter.js'; import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.iqzonertb.live'; @@ -101,7 +102,6 @@ describe('iqxBidAdapter', () => { expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ @@ -214,14 +214,6 @@ describe('iqxBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ diff --git a/test/spec/modules/iqzoneBidAdapter_spec.js b/test/spec/modules/iqzoneBidAdapter_spec.js index 2e920d3b769..f642a935f69 100644 --- a/test/spec/modules/iqzoneBidAdapter_spec.js +++ b/test/spec/modules/iqzoneBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/iqzoneBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'iqzone' +const bidder = 'iqzone'; describe('IQZoneBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('IQZoneBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('IQZoneBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -112,6 +136,7 @@ describe('IQZoneBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -129,7 +154,7 @@ describe('IQZoneBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +170,56 @@ describe('IQZoneBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +245,10 @@ describe('IQZoneBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +263,38 @@ describe('IQZoneBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -381,7 +484,7 @@ describe('IQZoneBidAdapter', function () { 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://cs.smartssp.iqzone.com/image?pbjs=1&gdpr=1&gdpr_consent=ALL&coppa=0') + expect(syncData[0].url).to.equal('https://cs.iqzone.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({}, {}, {}, { @@ -392,7 +495,19 @@ describe('IQZoneBidAdapter', function () { 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://cs.smartssp.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + expect(syncData[0].url).to.equal('https://cs.iqzone.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.iqzone.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/ixBidAdapter_spec.js b/test/spec/modules/ixBidAdapter_spec.js index 88cfb93850f..e0162617be3 100644 --- a/test/spec/modules/ixBidAdapter_spec.js +++ b/test/spec/modules/ixBidAdapter_spec.js @@ -821,8 +821,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true, - defaultForSlots: 1 + paapi: { + enabled: true + }, }; const DEFAULT_OPTION_FLEDGE_ENABLED = { @@ -843,7 +844,9 @@ describe('IndexexchangeAdapter', function () { tid: 'mock-tid' } }, - fledgeEnabled: true + paapi: { + enabled: true + } }; const DEFAULT_IDENTITY_RESPONSE = { @@ -853,7 +856,8 @@ describe('IndexexchangeAdapter', function () { source: 'identityinc.com', uids: [ { - id: 'identityid' + id: 'identityid', + atype: 1 } ] } @@ -1348,34 +1352,6 @@ describe('IndexexchangeAdapter', function () { }); }); - 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 = extractPayload(request[0]); - 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(11); - expect(payload.user.eids).to.deep.include(DEFAULT_USERID_PAYLOAD[0]); - }); - }); - }); - describe('buildRequestsIdentity', function () { let request; let payload; @@ -1408,8 +1384,14 @@ describe('IndexexchangeAdapter', function () { it('identity data in impression should have correct format and value (single identity partner)', function () { const impression = payload.user.eids; + expect(impression).to.be.an('array'); + expect(impression).to.have.lengthOf(1); expect(impression[0].source).to.equal(testCopy.IdentityIp.data.source); + expect(impression[0].uids).to.be.an('array'); + expect(impression[0].uids).to.have.lengthOf(1); expect(impression[0].uids[0].id).to.equal(testCopy.IdentityIp.data.uids[0].id); + expect(impression[0].uids[0].atype).to.exist; + expect(impression[0].uids[0].atype).to.equal(testCopy.IdentityIp.data.uids[0].atype); }); }); @@ -1545,9 +1527,7 @@ describe('IndexexchangeAdapter', function () { body: { ext: { pbjs_allow_all_eids: { - test: { - activated: false - } + activated: true } } } @@ -1562,11 +1542,6 @@ describe('IndexexchangeAdapter', function () { afterEach(function () { delete window.headertag; - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: false - } - }; validIdentityResponse = {} }); @@ -1580,12 +1555,6 @@ describe('IndexexchangeAdapter', function () { }); it('IX adapter filters eids from prebid past the maximum eid limit', function () { - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: true - } - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); let eid_sent_from_prebid = generateEid(55); cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); @@ -1627,12 +1596,6 @@ describe('IndexexchangeAdapter', function () { } } }; - serverResponse.body.ext.features = { - pbjs_allow_all_eids: { - activated: true - } - }; - FEATURE_TOGGLES.setFeatureToggles(serverResponse); const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); let eid_sent_from_prebid = generateEid(49); cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); @@ -1653,15 +1616,21 @@ describe('IndexexchangeAdapter', function () { expect(payload.ext.ixdiag.eidLength).to.equal(49); }); - it('All incoming eids are from unsupported source with feature toggle off', function () { - FEATURE_TOGGLES.setFeatureToggles(serverResponse); + it('Has incoming eids with no uid', function () { const cloneValidBid = utils.deepClone(DEFAULT_VIDEO_VALID_BID); - let eid_sent_from_prebid = generateEid(20); + let eid_sent_from_prebid = [ + { + source: 'catijah.org' + }, + { + source: 'bagel.com' + } + ]; cloneValidBid[0].userIdAsEids = utils.deepClone(eid_sent_from_prebid); const request = spec.buildRequests(cloneValidBid, DEFAULT_OPTION)[0]; const payload = extractPayload(request); expect(payload.user.eids).to.be.undefined - expect(payload.ext.ixdiag.eidLength).to.equal(20); + expect(payload.ext.ixdiag.eidLength).to.equal(2); }); it('We continue to send in IXL identity info and Prebid takes precedence over IXL', function () { @@ -3464,16 +3433,7 @@ describe('IndexexchangeAdapter', function () { expect(impression.ext.ae).to.equal(1); }); - it('impression should have ae=1 in ext when fledge module is enabled globally and default is set through setConfig', function () { - const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED_GLOBALLY); - const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID[0]); - const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; - const impression = extractPayload(requestBidFloor).imp[0]; - - expect(impression.ext.ae).to.equal(1); - }); - - it('impression should have ae=1 in ext when fledge module is enabled globally but no default set through setConfig but set at ad unit level', function () { + it('impression should have ae=1 in ext when request has paapi.enabled = true and ext.ae = 1', function () { const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); const bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; @@ -3517,6 +3477,22 @@ describe('IndexexchangeAdapter', function () { expect(logWarnSpy.calledWith('error setting auction environment flag - must be an integer')).to.be.true; logWarnSpy.restore(); }); + + it('impression should have paapi extension when passed', function () { + const bidderRequest = deepClone(DEFAULT_OPTION_FLEDGE_ENABLED); + let bid = utils.deepClone(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED[0]); + bid.ortb2Imp.ext.ae = 1 + bid.ortb2Imp.ext.paapi = { + requestedSize: { + width: 300, + height: 250 + } + } + const requestBidFloor = spec.buildRequests([bid], bidderRequest)[0]; + const impression = extractPayload(requestBidFloor).imp[0]; + expect(impression.ext.paapi.requestedSize.width).to.equal(300); + expect(impression.ext.paapi.requestedSize.height).to.equal(250); + }); }); describe('integration through exchangeId and externalId', function () { @@ -4164,7 +4140,7 @@ describe('IndexexchangeAdapter', function () { beforeEach(() => { bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; serverResponseWithoutFledgeConfigs = { body: { @@ -4228,17 +4204,17 @@ describe('IndexexchangeAdapter', function () { } } ]; - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedOutput); + expect(result.paapi).to.deep.equal(expectedOutput); }); it('should correctly interpret response without auction configs', () => { const result = spec.interpretResponse(serverResponseWithoutFledgeConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; }); it('should handle malformed auction configs gracefully', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfig, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.empty; + expect(result.paapi).to.be.empty; }); it('should log warning for malformed auction configs', () => { @@ -4250,7 +4226,7 @@ describe('IndexexchangeAdapter', function () { it('should return bids when protected audience auction conigs is malformed', () => { const result = spec.interpretResponse(serverResponseWithMalformedAuctionConfigs, bidderRequestWithFledgeEnabled); - expect(result.fledgeAuctionConfigs).to.be.undefined; + expect(result.paapi).to.be.undefined; expect(result.length).to.be.greaterThan(0); }); }); @@ -4269,7 +4245,7 @@ describe('IndexexchangeAdapter', function () { }; bidderRequestWithFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID_WITH_FLEDGE_ENABLED, {})[0]; - bidderRequestWithFledgeEnabled.fledgeEnabled = true; + bidderRequestWithFledgeEnabled.paapi = {enabled: true}; bidderRequestWithoutFledgeEnabled = spec.buildRequests(DEFAULT_BANNER_VALID_BID, {})[0]; }); diff --git a/test/spec/modules/justIdSystem_spec.js b/test/spec/modules/justIdSystem_spec.js index b6a8cd2d310..95d964d807c 100644 --- a/test/spec/modules/justIdSystem_spec.js +++ b/test/spec/modules/justIdSystem_spec.js @@ -143,7 +143,7 @@ describe('JustIdSystem', function () { var scriptTagCallback; beforeEach(() => { - loadExternalScriptStub.callsFake((url, moduleCode, callback) => { + loadExternalScriptStub.callsFake((url, moduleCode, moduleType, callback) => { scriptTagCallback = callback; return scriptTag; }); diff --git a/test/spec/modules/jwplayerRtdProvider_spec.js b/test/spec/modules/jwplayerRtdProvider_spec.js index c57c8a685e7..58cfc751a4f 100644 --- a/test/spec/modules/jwplayerRtdProvider_spec.js +++ b/test/spec/modules/jwplayerRtdProvider_spec.js @@ -12,6 +12,7 @@ import { getVatFromCache, getVatFromPlayer, setOverrides, + getPlayer, jwplayerSubmodule } from 'modules/jwplayerRtdProvider.js'; import {server} from 'test/mocks/xhr.js'; @@ -629,7 +630,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2Fragments.global).to.have.property('site'); expect(ortb2Fragments.global.site).to.have.property('content'); - expect(ortb2Fragments.global.site.content).to.have.property('id', 'jw_' + testIdForSuccess); + expect(ortb2Fragments.global.site.content).to.have.property('id', 'randomContentId'); expect(ortb2Fragments.global.site.content).to.have.property('data'); const data = ortb2Fragments.global.site.content.data; expect(data).to.have.length(3); @@ -801,7 +802,7 @@ describe('jwplayerRtdProvider', function() { describe(' Add Ortb Site Content', function () { beforeEach(() => { setOverrides({ - overrideContentId: 'always', + overrideContentId: 'whenEmpty', overrideContentUrl: 'whenEmpty', overrideContentTitle: 'whenEmpty', overrideContentDescription: 'whenEmpty' @@ -865,16 +866,16 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; + const newId = 'newId'; const expectedUrl = 'expectedUrl'; const expectedTitle = 'expectedTitle'; const expectedDescription = 'expectedDescription'; const expectedData = { datum: 'datum' }; - addOrtbSiteContent(ortb2, expectedId, expectedData, expectedTitle, expectedDescription, expectedUrl); + addOrtbSiteContent(ortb2, newId, expectedData, expectedTitle, expectedDescription, expectedUrl); expect(ortb2).to.have.nested.property('site.random.random_sub', 'randomSub'); expect(ortb2).to.have.nested.property('app.content.id', 'appId'); expect(ortb2).to.have.nested.property('site.content.ext.random_field', 'randomField'); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); expect(ortb2).to.have.nested.property('site.content.url', expectedUrl); expect(ortb2).to.have.nested.property('site.content.title', expectedTitle); expect(ortb2).to.have.nested.property('site.content.ext.description', expectedDescription); @@ -889,7 +890,7 @@ describe('jwplayerRtdProvider', function() { expect(ortb2).to.have.nested.property('site.content.id', expectedId); }); - it('should override content id by default', function () { + it('should keep old content id by default', function () { const ortb2 = { site: { content: { @@ -898,9 +899,8 @@ describe('jwplayerRtdProvider', function() { } }; - const expectedId = 'expectedId'; - addOrtbSiteContent(ortb2, expectedId); - expect(ortb2).to.have.nested.property('site.content.id', expectedId); + addOrtbSiteContent(ortb2, 'newId'); + expect(ortb2).to.have.nested.property('site.content.id', 'oldId'); }); it('should keep previous content id when new value is not available', function () { @@ -1604,6 +1604,66 @@ describe('jwplayerRtdProvider', function() { }); }); + describe('Player detection', function () { + const playerInstanceMock = { + getPlaylist: () => [], + getPlaylistItem: () => ({}) + }; + + beforeEach(function () { + window.jwplayer = sinon.stub(); + }); + + afterEach(function () { + delete window.jwplayer; + }); + + it('should fail if jwplayer global does not exist', function () { + delete window.jwplayer; + expect(getPlayer('divId')).to.be.undefined; + }); + + it('should return the player instance for the specified div id', function () { + window.jwplayer.returns(playerInstanceMock); + const player = getPlayer('divId'); + expect(player).to.deep.equal(playerInstanceMock); + }); + + it('should request a player when the div id does not match a player on the page and only 1 player is in the DOM', function () { + const playerDomElement = document.createElement('div'); + playerDomElement.className = 'jwplayer'; + document.body.appendChild(playerDomElement); + + window.jwplayer.withArgs('invalidDivId').returns(undefined); + window.jwplayer.returns(playerInstanceMock); + + const playerInstance = getPlayer('invalidDivId'); + + expect(playerInstance).to.deep.equal(playerInstanceMock); + + document.body.removeChild(playerDomElement); + }); + + it('should fail when the div id does not match a player on the page, and multiple players are instantiated', function () { + const firstPlayerDomElement = document.createElement('div'); + const secondPlayerDomElement = document.createElement('div'); + firstPlayerDomElement.className = 'jwplayer'; + secondPlayerDomElement.className = 'jwplayer'; + document.body.appendChild(firstPlayerDomElement); + document.body.appendChild(secondPlayerDomElement); + + window.jwplayer.withArgs('invalidDivId').returns(undefined); + window.jwplayer.returns(playerInstanceMock); + + const playerInstance = getPlayer('invalidDivId'); + + expect(playerInstance).to.be.undefined; + + document.body.removeChild(firstPlayerDomElement); + document.body.removeChild(secondPlayerDomElement); + }); + }); + describe('jwplayerSubmodule', function () { it('successfully instantiates', function () { expect(jwplayerSubmodule.init()).to.equal(true); diff --git a/test/spec/modules/kargoBidAdapter_spec.js b/test/spec/modules/kargoBidAdapter_spec.js index ee89d6468a5..a9121a7a59f 100644 --- a/test/spec/modules/kargoBidAdapter_spec.js +++ b/test/spec/modules/kargoBidAdapter_spec.js @@ -300,7 +300,7 @@ describe('kargo adapter tests', function() { domain, isAmp: false, location: topUrl, - numIframs: 0, + numIframes: 0, page: topUrl, reachedTop: true, ref: referer, @@ -428,12 +428,12 @@ describe('kargo adapter tests', function() { } } }]); - expect(payload.ext).to.deep.equal({ ortb2: { + expect(payload.ext.ortb2).to.deep.equal({ user: { key: 'value' } - }}); + }); payload = getPayloadFromTestBids(testBids); - expect(payload.ext).to.be.undefined; + expect(payload.ext.ortb2).to.be.undefined; payload = getPayloadFromTestBids([{ ...minimumBidParams, @@ -450,9 +450,33 @@ describe('kargo adapter tests', function() { } } }]); - expect(payload.ext).to.deep.equal({ortb2: { + expect(payload.ext.ortb2).to.deep.equal({ user: { key: 'value' } - }}); + } + ); + }); + + it('copies the refererInfo object from bidderRequest if present', function() { + let payload; + payload = getPayloadFromTestBids(testBids); + expect(payload.ext.refererInfo).to.deep.equal({ + canonicalUrl: 'https://random.com/this/is/a/url', + domain: 'random.com', + isAmp: false, + location: 'https://random.com/this/is/a/url', + numIframes: 0, + page: 'https://random.com/this/is/a/url', + reachedTop: true, + ref: 'https://random.com/', + stack: [ + 'https://random.com/this/is/a/url' + ], + topmostLocation: 'https://random.com/this/is/a/url' + }); + + delete bidderRequest.refererInfo + payload = getPayloadFromTestBids(testBids); + expect(payload.ext).to.be.undefined; }); it('pulls the site category from the first bids ortb2 object', function() { @@ -1808,7 +1832,7 @@ describe('kargo adapter tests', function() { }); }); - it('should return fledgeAuctionConfigs if provided in bid response', function () { + it('should return paapi if provided in bid response', function () { const auctionConfig = { seller: 'https://kargo.com', decisionLogicUrl: 'https://kargo.com/decision_logic.js', @@ -1841,11 +1865,11 @@ describe('kargo adapter tests', function() { expect(bid).to.have.property('meta').that.is.an('object'); }); - // Test properties of fledgeAuctionConfigs - expect(result.fledgeAuctionConfigs).to.have.lengthOf(3); + // Test properties of paapi + expect(result.paapi).to.have.lengthOf(3); const expectedBidIds = ['1', '3', '5']; // Expected bidIDs - result.fledgeAuctionConfigs.forEach(config => { + result.paapi.forEach(config => { expect(config).to.have.property('bidId'); expect(expectedBidIds).to.include(config.bidId); @@ -1861,16 +1885,15 @@ describe('kargo adapter tests', function() { describe('getUserSyncs', function() { let crb = {}; const clientId = 'random-client-id-string'; - const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; + const baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid='; - function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&idx=0&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { + function buildSyncUrls(baseUrl = 'https://crb.kargo.com/api/v1/initsyncrnd/random-client-id-string?seed=3205e885-8d37-4139-b47e-f82cff268000&gdpr=0&gdpr_consent=&us_privacy=&gpp=&gpp_sid=') { let syncs = []; - for (let i = 0; i < 5; i++) { - syncs.push({ - type: 'iframe', - url: baseUrl.replace(/idx=\d+&/, `idx=${i}&`), - }); - } + + syncs.push({ + type: 'iframe', + url: baseUrl + }); return syncs; } @@ -2007,31 +2030,49 @@ describe('kargo adapter tests', function() { }); }); - describe('onTimeout', function() { + describe('onTimeout', function () { + let fetchStub; + beforeEach(function () { - sinon.stub(utils, 'triggerPixel'); + fetchStub = sinon.stub(global, 'fetch').resolves(); // Stub fetch globally }); afterEach(function () { - utils.triggerPixel.restore(); + fetchStub.restore(); // Restore the original fetch function }); - it('does not call triggerPixel if timeout data is not provided', function() { + it('does not call fetch if timeout data is not provided', function () { spec.onTimeout(null); - expect(utils.triggerPixel.callCount).to.equal(0); + expect(fetchStub.callCount).to.equal(0); }); - it('calls triggerPixel if any timeout data is provided', function() { + it('calls fetch with the correct URLs if timeout data is provided', function () { spec.onTimeout([ - {auctionId: 'test-auction-id', timeout: 400}, - {auctionId: 'test-auction-id-2', timeout: 100}, - {auctionId: 'test-auction-id-3', timeout: 450}, - {auctionId: 'test-auction-id-4', timeout: 500}, + { auctionId: 'test-auction-id', timeout: 400 }, + { auctionId: 'test-auction-id-2', timeout: 100 }, + { auctionId: 'test-auction-id-3', timeout: 450 }, + { auctionId: 'test-auction-id-4', timeout: 500 }, ]); - expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400')).to.be.true; - expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100')).to.be.true; - expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450')).to.be.true; - expect(utils.triggerPixel.calledWith('https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500')).to.be.true; + + expect(fetchStub.calledWith( + 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id&ato=400', + { method: 'GET', keepalive: true } + )).to.be.true; + + expect(fetchStub.calledWith( + 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-2&ato=100', + { method: 'GET', keepalive: true } + )).to.be.true; + + expect(fetchStub.calledWith( + 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-3&ato=450', + { method: 'GET', keepalive: true } + )).to.be.true; + + expect(fetchStub.calledWith( + 'https://krk2.kargo.com/api/v1/event/timeout?aid=test-auction-id-4&ato=500', + { method: 'GET', keepalive: true } + )).to.be.true; }); }); }); diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js index 1480f1cc768..739a970603c 100644 --- a/test/spec/modules/kimberliteBidAdapter_spec.js +++ b/test/spec/modules/kimberliteBidAdapter_spec.js @@ -1,6 +1,6 @@ -import { spec } from 'modules/kimberliteBidAdapter.js'; +import { spec, ENDPOINT_URL, expandAuctionMacros } from 'modules/kimberliteBidAdapter.js'; import { assert } from 'chai'; -import { BANNER } from '../../../src/mediaTypes.js'; +import { BANNER, VIDEO } from '../../../src/mediaTypes.js'; const BIDDER_CODE = 'kimberlite'; @@ -8,74 +8,104 @@ describe('kimberliteBidAdapter', function () { const sizes = [[640, 480]]; describe('isBidRequestValid', function () { - let bidRequest; + let bidRequests; beforeEach(function () { - bidRequest = { - mediaTypes: { - [BANNER]: { - sizes: [[320, 240]] + bidRequests = [ + { + mediaTypes: { + [BANNER]: { + sizes: [[320, 240]] + } + }, + params: { + placementId: 'test-placement' } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } } - }; + ]; }); - it('pass on valid bidRequest', function () { - assert.isTrue(spec.isBidRequestValid(bidRequest)); + it('pass on valid banner bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[0])); }); it('fails on missed placementId', function () { - delete bidRequest.params.placementId; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].params.placementId; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner', function () { - delete bidRequest.mediaTypes.banner; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty banner.sizes', function () { - delete bidRequest.mediaTypes.banner.sizes; - assert.isFalse(spec.isBidRequestValid(bidRequest)); + delete bidRequests[0].mediaTypes.banner.sizes; + assert.isFalse(spec.isBidRequestValid(bidRequests[0])); }); it('fails on empty request', function () { assert.isFalse(spec.isBidRequestValid()); }); + + it('pass on valid video bidRequest', function () { + assert.isTrue(spec.isBidRequestValid(bidRequests[1])); + }); + + it('fails on missed video.mimes', function () { + delete bidRequests[1].mediaTypes.video.mimes; + assert.isFalse(spec.isBidRequestValid(bidRequests[1])); + }); }); describe('buildRequests', function () { let bidRequests, bidderRequest; beforeEach(function () { - bidRequests = [{ - mediaTypes: { - [BANNER]: {sizes: sizes} + bidRequests = [ + { + mediaTypes: { + [BANNER]: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - params: { - placementId: 'test-placement' + { + mediaTypes: { + [VIDEO]: { + mimes: ['video/mp4'], + } + }, + params: { + placementId: 'test-placement' + } } - }]; + ]; bidderRequest = { refererInfo: { domain: 'example.com', page: 'https://www.example.com/test.html', - }, - bids: [{ - mediaTypes: { - [BANNER]: {sizes: sizes} - } - }] + } }; }); it('valid bid request', function () { const bidRequest = spec.buildRequests(bidRequests, bidderRequest); + assert.equal(bidRequest.method, 'POST'); + assert.equal(bidRequest.url, ENDPOINT_URL); assert.ok(bidRequest.data); const requestData = bidRequest.data; @@ -87,10 +117,10 @@ describe('kimberliteBidAdapter', function () { expect(requestData.ext).to.be.an('Object').and.have.all.keys('prebid'); expect(requestData.ext.prebid).to.be.an('Object').and.have.all.keys('ver', 'adapterVer'); - const impData = requestData.imp[0]; - expect(impData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); + const impBannerData = requestData.imp[0]; + expect(impBannerData.banner).is.to.be.an('Object').and.have.all.keys(['format', 'topframe']); - const bannerData = impData.banner; + const bannerData = impBannerData.banner; expect(bannerData.format).to.be.an('array').and.is.not.empty; const formatData = bannerData.format[0]; @@ -98,74 +128,133 @@ describe('kimberliteBidAdapter', function () { assert.equal(formatData.w, sizes[0][0]); assert.equal(formatData.h, sizes[0][1]); + + if (FEATURES.VIDEO) { + const impVideoData = requestData.imp[1]; + expect(impVideoData.video).is.to.be.an('Object').and.have.all.keys(['mimes']); + + const videoData = impVideoData.video; + expect(videoData.mimes).to.be.an('array').and.is.not.empty; + expect(videoData.mimes[0]).to.be.a('string').that.equals('video/mp4'); + } }); }); describe('interpretResponse', function () { - let bidderResponse, bidderRequest, bidRequest, expectedBid; + let bidderResponse, bidderRequest, bidRequest, expectedBids; const requestId = '07fba8b0-8812-4dc6-b91e-4a525d81729c'; - const bidId = '222209853178'; - const impId = 'imp-id'; - const crId = 'creative-id'; - const adm = 'landing'; + const bannerAdm = 'landing'; + const videoAdm = 'http://video-test.landing.com?p=${AUCTION_PRICE}&c=${AUCTION_CURRENCY}test vast'; + const nurl = 'http://nurl.landing.com?p=${AUCTION_PRICE}&c=${AUCTION_CURRENCY}'; + const nurlPixel = `
`; - beforeEach(function () { - bidderResponse = { - body: { - id: requestId, - seatbid: [{ - bid: [{ - crid: crId, - id: bidId, - impid: impId, - price: 1, - adm: adm + const currencies = [ + undefined, + 'USD' + ]; + + currencies.forEach(function(currency) { + beforeEach(function () { + bidderResponse = { + body: { + id: requestId, + seatbid: [{ + bid: [ + { + crid: 1, + impid: 1, + price: 1, + adm: bannerAdm, + nurl: nurl + }, + { + crid: 2, + impid: 2, + price: 1, + adm: videoAdm + } + ] }] - }] - } - }; + } + }; - bidderRequest = { - refererInfo: { - domain: 'example.com', - page: 'https://www.example.com/test.html', - }, - bids: [{ - bidId: impId, - mediaTypes: { - [BANNER]: {sizes: sizes} + bidderRequest = { + refererInfo: { + domain: 'example.com', + page: 'https://www.example.com/test.html', }, - params: { - placementId: 'test-placement' - } - }] - }; + bids: [ + { + bidId: 1, + mediaTypes: { + banner: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } + }, + { + bidId: 2, + mediaTypes: { + video: { + mimes: ['video/mp4'] + } + }, + params: { + placementId: 'test-placement' + } + } + ] + }; - expectedBid = { - mediaType: 'banner', - requestId: 'imp-id', - seatBidId: '222209853178', - cpm: 1, - creative_id: 'creative-id', - creativeId: 'creative-id', - ttl: 300, - netRevenue: true, - ad: adm, - meta: {} - }; + expectedBids = [ + { + mediaType: 'banner', + requestId: 1, + cpm: 1, + creative_id: 1, + creativeId: 1, + ttl: 300, + netRevenue: true, + ad: bannerAdm + nurlPixel, + meta: {} + }, + { + mediaType: 'video', + requestId: 2, + cpm: 1, + creative_id: 2, + creativeId: 2, + ttl: 300, + netRevenue: true, + vastXml: videoAdm, + meta: {} + }, + ]; - bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); - }); + if (currency) { + expectedBids[0].currency = expectedBids[1].currency = bidderResponse.body.cur = currency; + } - it('pass on valid request', function () { - const bids = spec.interpretResponse(bidderResponse, bidRequest); - assert.deepEqual(bids[0], expectedBid); - }); + bidRequest = spec.buildRequests(bidderRequest.bids, bidderRequest); + }); + + it('pass on valid request', function () { + const bids = spec.interpretResponse(bidderResponse, bidRequest); + expectedBids[0].ad = expandAuctionMacros(expectedBids[0].ad, expectedBids[0].cpm, bidderResponse.body.cur); + assert.deepEqual(bids[0], expectedBids[0]); + if (FEATURES.VIDEO) { + expectedBids[1].vastXml = + expandAuctionMacros(expectedBids[1].vastXml, expectedBids[1].cpm, bidderResponse.body.cur); + assert.deepEqual(bids[1], expectedBids[1]); + } + }); - it('fails on empty response', function () { - const bids = spec.interpretResponse({body: ''}, bidRequest); - assert.empty(bids); + it('fails on empty response', function () { + const bids = spec.interpretResponse({body: ''}, bidRequest); + assert.empty(bids); + }); }); }); }); diff --git a/test/spec/modules/kinessoIdSystem_spec.js b/test/spec/modules/kinessoIdSystem_spec.js new file mode 100644 index 00000000000..e5d9721737d --- /dev/null +++ b/test/spec/modules/kinessoIdSystem_spec.js @@ -0,0 +1,26 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {kinessoIdSubmodule} from '../../../modules/kinessoIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; + +describe('kinesso ID', () => { + describe('eid', () => { + before(() => { + attachIdSystem(kinessoIdSubmodule); + }); + 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 + }] + }); + }); + }); +}); diff --git a/test/spec/modules/kiviadsBidAdapter_spec.js b/test/spec/modules/kiviadsBidAdapter_spec.js index 03d58cbc265..e2d23d06822 100644 --- a/test/spec/modules/kiviadsBidAdapter_spec.js +++ b/test/spec/modules/kiviadsBidAdapter_spec.js @@ -3,11 +3,21 @@ import { spec } from '../../../modules/kiviadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'kiviads' +const bidder = 'kiviads'; const adUrl = 'https://lb.kiviads.com/pbjs'; const syncUrl = 'https://sync.kiviads.com'; describe('KiviAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('KiviAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,11 +88,22 @@ describe('KiviAdsBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - bidderTimeout: 300 + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -114,6 +138,7 @@ describe('KiviAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -122,7 +147,6 @@ describe('KiviAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'gpp', 'tmax' ); expect(data.deviceWidth).to.be.a('number'); @@ -130,11 +154,9 @@ describe('KiviAdsBidAdapter', function () { expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); - expect(data.host).to.contain('localhost'); expect(data.page).to.be.a('string'); - expect(data.page).to.equal('/'); expect(data.coppa).to.be.a('number'); - expect(data.gdpr).to.be.a('string'); + expect(data.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -150,6 +172,56 @@ describe('KiviAdsBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -175,8 +247,10 @@ describe('KiviAdsBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -191,12 +265,38 @@ describe('KiviAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -400,5 +500,17 @@ describe('KiviAdsBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`) + }); }); }); diff --git a/test/spec/modules/koblerBidAdapter_spec.js b/test/spec/modules/koblerBidAdapter_spec.js index 2b5830f68d2..74c0a1f5967 100644 --- a/test/spec/modules/koblerBidAdapter_spec.js +++ b/test/spec/modules/koblerBidAdapter_spec.js @@ -5,14 +5,37 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getRefererInfo} from 'src/refererDetection.js'; -function createBidderRequest(auctionId, timeout, pageUrl) { +function createBidderRequest(auctionId, timeout, pageUrl, addGdprConsent) { + const gdprConsent = addGdprConsent ? { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + apiVersion: 2, + vendorData: { + purpose: { + consents: { + 1: false, + 2: true, + 3: false + } + }, + publisher: { + restrictions: { + '2': { + // require consent + '11': 1 + } + } + } + }, + gdprApplies: true + } : {}; return { bidderRequestId: 'mock-uuid', auctionId: auctionId || 'c1243d83-0bed-4fdb-8c76-42b456be17d0', timeout: timeout || 2000, refererInfo: { page: pageUrl || 'example.com' - } + }, + gdprConsent: gdprConsent }; } @@ -289,27 +312,6 @@ describe('KoblerAdapter', function () { expect(openRtbRequest.test).to.be.equal(1); }); - it('should read pageUrl from config when testing', function () { - config.setConfig({ - pageUrl: 'https://testing-url.com' - }); - const validBidRequests = [ - createValidBidRequest( - { - test: true - } - ) - ]; - const bidderRequest = createBidderRequest(); - - const result = spec.buildRequests(validBidRequests, bidderRequest); - expect(result.url).to.be.equal('https://bid-service.dev.essrtb.com/bid/prebid_rtb_call'); - - const openRtbRequest = JSON.parse(result.data); - expect(openRtbRequest.site.page).to.be.equal('https://testing-url.com'); - expect(openRtbRequest.test).to.be.equal(1); - }); - it('should not read pageUrl from config when not testing', function () { config.setConfig({ pageUrl: 'https://testing-url.com' @@ -439,7 +441,8 @@ describe('KoblerAdapter', function () { const bidderRequest = createBidderRequest( '9ff580cf-e10e-4b66-add7-40ac0c804e21', 4500, - 'bid.kobler.no' + 'bid.kobler.no', + true ); const result = spec.buildRequests(validBidRequests, bidderRequest); @@ -529,7 +532,13 @@ describe('KoblerAdapter', function () { site: { page: 'bid.kobler.no' }, - test: 0 + test: 0, + ext: { + kobler: { + tcf_purpose_2_given: true, + tcf_purpose_3_given: false + } + } }; expect(openRtbRequest).to.deep.equal(expectedOpenRtbRequest); @@ -702,14 +711,12 @@ describe('KoblerAdapter', function () { spec.onTimeout([ { adUnitCode: 'adunit-code', - auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ef236c6c-e934-406b-a877-d7be8e8a839a', timeout: 100, params: [], }, { adUnitCode: 'adunit-code-2', - auctionId: 'a1fba829-dd41-409f-acfb-b7b0ac5f30c6', bidId: 'ca4121c8-9a4a-46ba-a624-e9b64af206f2', timeout: 100, params: [], @@ -719,13 +726,11 @@ describe('KoblerAdapter', function () { 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&' + - 'page_url=' + encodeURIComponent(getRefererInfo().page) + 'bid_id=ef236c6c-e934-406b-a877-d7be8e8a839a&timeout=100&page_url=' + encodeURIComponent(getRefererInfo().page) ); 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&' + - 'page_url=' + encodeURIComponent(getRefererInfo().page) + 'bid_id=ca4121c8-9a4a-46ba-a624-e9b64af206f2&timeout=100&page_url=' + encodeURIComponent(getRefererInfo().page) ); }); }); diff --git a/test/spec/modules/krushmediaBidAdapter_spec.js b/test/spec/modules/krushmediaBidAdapter_spec.js index fcdcc942290..516b587edff 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -1,147 +1,304 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/krushmediaBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/krushmediaBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'krushmedia'; describe('KrushmediabBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'krushmedia', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + key: 783 + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + key: 783 + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + key: 783 + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - key: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.key; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://ads4.krushmedia.com/?c=rtb&m=hb'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('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.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('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 placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.key).to.be.equal(783); + expect(placement.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + 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 valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids } - }; + ]; - 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); + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.traffic).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.traffic === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.wPlayer).to.be.an('number'); + expect(placement.hPlayer).to.be.an('number'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); 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); + 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; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -156,7 +313,11 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -164,15 +325,15 @@ describe('KrushmediabBidAdapter', function () { 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('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.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { @@ -186,7 +347,11 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -220,6 +385,10 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -306,6 +475,7 @@ describe('KrushmediabBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -315,20 +485,32 @@ describe('KrushmediabBidAdapter', function () { 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].type).to.equal('image') 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') + expect(syncData[0].url).to.equal('https://cs.krushmedia.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: '1NNN' + 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://cs.krushmedia.com/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] }); 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].type).to.equal('image') expect(syncData[0].url).to.be.a('string') - expect(syncData[0].url).to.equal('https://cs.krushmedia.com/html?src=pbjs&ccpa_consent=1NNN') + expect(syncData[0].url).to.equal('https://cs.krushmedia.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') }); }); }); diff --git a/test/spec/modules/kueezRtbBidAdapter_spec.js b/test/spec/modules/kueezRtbBidAdapter_spec.js index ebd11885af4..2df5e299aeb 100644 --- a/test/spec/modules/kueezRtbBidAdapter_spec.js +++ b/test/spec/modules/kueezRtbBidAdapter_spec.js @@ -2,6 +2,14 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/kueezRtbBidAdapter.js'; +import * as utils from 'src/utils.js'; +import {version} from 'package.json'; +import {useFakeTimers} from 'sinon'; +import {BANNER, VIDEO} from '../../../src/mediaTypes'; +import {config} from '../../../src/config'; +import { hashCode, extractPID, extractCID, @@ -10,12 +18,7 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/kueezRtbBidAdapter.js'; -import * as utils from 'src/utils.js'; -import {version} from 'package.json'; -import {useFakeTimers} from 'sinon'; -import {BANNER, VIDEO} from '../../../src/mediaTypes'; -import {config} from '../../../src/config'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -101,9 +104,15 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -322,7 +331,14 @@ describe('KueezRtbBidAdapter', function () { startdelay: 0 } }, - gpid: '' + gpid: '', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -384,6 +400,13 @@ describe('KueezRtbBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -526,8 +549,6 @@ describe('KueezRtbBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -580,13 +601,13 @@ describe('KueezRtbBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -594,7 +615,7 @@ describe('KueezRtbBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -618,8 +639,8 @@ describe('KueezRtbBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -630,7 +651,7 @@ describe('KueezRtbBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/lassoBidAdapter_spec.js b/test/spec/modules/lassoBidAdapter_spec.js index ad4040c0452..15dee20e566 100644 --- a/test/spec/modules/lassoBidAdapter_spec.js +++ b/test/spec/modules/lassoBidAdapter_spec.js @@ -62,14 +62,14 @@ describe('lassoBidAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return true when there are extra params', function () { - const bid = Object.assign({}, bid, { + const invalidBid = Object.assign({}, bid, { params: { adUnitId: 123456, zone: 1, publisher: 'test' } }) - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when there are no params', function () { const invalidBid = { ...bid }; diff --git a/test/spec/modules/limelightDigitalBidAdapter_spec.js b/test/spec/modules/limelightDigitalBidAdapter_spec.js index 51cf8cd14ee..b13beb26d28 100644 --- a/test/spec/modules/limelightDigitalBidAdapter_spec.js +++ b/test/spec/modules/limelightDigitalBidAdapter_spec.js @@ -26,7 +26,11 @@ describe('limelightDigitalAdapter', function () { }, ortb2Imp: { ext: { - tid: '3bb2f6da-87a6-4029-aeb0-bfe951372e62', + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -70,7 +74,11 @@ describe('limelightDigitalAdapter', function () { sizes: [[350, 200]], ortb2Imp: { ext: { - tid: '068867d1-46ec-40bb-9fa0-e24611786fb4', + gpid: '/1111/homepage#300x250', + tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -120,7 +128,11 @@ describe('limelightDigitalAdapter', function () { sizes: [[800, 600]], ortb2Imp: { ext: { + gpid: '/1111/homepage#300x250', tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -169,7 +181,11 @@ describe('limelightDigitalAdapter', function () { }, ortb2Imp: { ext: { + gpid: '/1111/homepage#300x250', tid: '738d5915-6651-43b9-9b6b-d50517350917', + data: { + 'pbadslot': '/1111/homepage#300x250' + } } }, userIdAsEids: [ @@ -235,7 +251,9 @@ describe('limelightDigitalAdapter', function () { 'secure', 'adUnits', 'sua', - 'page' + 'page', + 'ortb2', + 'refererInfo' ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); @@ -254,7 +272,8 @@ describe('limelightDigitalAdapter', function () { 'custom2', 'custom3', 'custom4', - 'custom5' + 'custom5', + 'ortb2Imp' ); expect(adUnit.id).to.be.a('number'); expect(adUnit.bidId).to.be.a('string'); @@ -268,6 +287,7 @@ describe('limelightDigitalAdapter', function () { expect(adUnit.custom3).to.be.a('string'); expect(adUnit.custom4).to.be.a('string'); expect(adUnit.custom5).to.be.a('string'); + expect(adUnit.ortb2Imp).to.be.an('object'); }) expect(data.sua.browsers).to.be.a('array'); expect(data.sua.platform).to.be.a('array'); @@ -275,6 +295,7 @@ describe('limelightDigitalAdapter', function () { expect(data.sua.architecture).to.be.a('string'); expect(data.page).to.be.a('string'); expect(data.page).to.be.equal('testPage'); + expect(data.ortb2).to.be.an('object'); }) }) it('Returns valid URL', function () { @@ -719,4 +740,5 @@ function validateAdUnit(adUnit, bid) { expect(adUnit.publisherId).to.equal(bid.params.publisherId); expect(adUnit.userIdAsEids).to.deep.equal(bid.userIdAsEids); expect(adUnit.supplyChain).to.deep.equal(bid.schain); + expect(adUnit.ortb2Imp).to.deep.equal(bid.ortb2Imp); } diff --git a/test/spec/modules/liveIntentExternalIdSystem_spec.js b/test/spec/modules/liveIntentExternalIdSystem_spec.js new file mode 100644 index 00000000000..3e49eb5fc4b --- /dev/null +++ b/test/spec/modules/liveIntentExternalIdSystem_spec.js @@ -0,0 +1,463 @@ +import { liveIntentExternalIdSubmodule, resetSubmodule } from 'libraries/liveIntentId/externalIdSystem.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; +import * as refererDetection from '../../../src/refererDetection.js'; +const DEFAULT_AJAX_TIMEOUT = 5000 +const PUBLISHER_ID = '89899'; +const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; + +describe('LiveIntentExternalId', function() { + let uspConsentDataStub; + let gdprConsentDataStub; + let gppConsentDataStub; + let coppaConsentDataStub; + let refererInfoStub; + + beforeEach(function() { + uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); + gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); + gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); + refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); + }); + + afterEach(function() { + uspConsentDataStub.restore(); + gdprConsentDataStub.restore(); + gppConsentDataStub.restore(); + coppaConsentDataStub.restore(); + refererInfoStub.restore(); + window.liQHub = []; // reset + resetSubmodule(); + }); + + it('should use appId in integration when both appId and distributorId are provided', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + distributorId: 'did-1111', + liCollectConfig: { + appId: 'a-0001' + }, + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { appId: 'a-0001', publisherId: '89899', type: 'application' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { emailHash: '123' }, + type: 'collect' + }]) + }); + + it('should fire an event and resolve when getId and include the privacy settings into the resolution request', function () { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: true, + consentString: 'consentDataString' + }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1, 2] + }) + liveIntentExternalIdSubmodule.getId(defaultConfigParams).callback(() => {}); + + const expectedConsent = { gdpr: { consentString: 'consentDataString', gdprApplies: true }, gpp: { applicableSections: [1, 2], consentString: 'gppConsentDataString' }, usPrivacy: { consentString: '1YNY' } } + + expect(window.liQHub).to.have.length(2) + + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: expectedConsent, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId' ], + type: 'resolve' + }) + }); + + it('should fire an event when getId and a hash is provided', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + emailHash: '58131bc547fb87af94cebdaf3102321f' + }}).callback(() => {}); + + expect(window.liQHub).to.have.length(3) + + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + expect(window.liQHub[1]).to.eql({ + clientRef: {}, + sourceEvent: { emailHash: '58131bc547fb87af94cebdaf3102321f' }, + type: 'collect' + }) + + const resolveCommand = window.liQHub[2] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId' ], + type: 'resolve' + }) + }); + + it('should have the same data after call decode when appId, disrtributorId and sourceEvent is absent', function() { + liveIntentExternalIdSubmodule.decode({}, { + params: { + ...defaultConfigParams.params, + distributorId: undefined + } + }); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: undefined, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }]) + }); + + it('should have the same data after call decode when appId and sourceEvent is present', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + liCollectConfig: { + appId: 'a-0001' + }, + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { appId: 'a-0001', publisherId: '89899', type: 'application' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { emailHash: '123' }, + type: 'collect' + }]) + }); + + it('should have the same data after call decode when distributorId and sourceEvent is present', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + distributorId: 'did-1111', + emailHash: '123' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: 'did-1111', publisherId: defaultConfigParams.params.publisherId, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'did-1111', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { emailHash: '123' }, + type: 'collect' + }]) + }); + + it('should include the identifier data if it is present in config', function() { + const configParams = { + params: { + ...defaultConfigParams.params, + distributorId: 'did-1111', + emailHash: '123', + ipv4: 'foov4', + ipv6: 'foov6', + userAgent: 'bar' + } + } + liveIntentExternalIdSubmodule.decode({}, configParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: 'did-1111', publisherId: defaultConfigParams.params.publisherId, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'did-1111', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }, + { + clientRef: {}, + sourceEvent: { emailHash: '123', ipv4: 'foov4', ipv6: 'foov6', userAgent: 'bar' }, + type: 'collect' + }]) + }); + + it('should have the same data when decode with privacy settings', function() { + uspConsentDataStub.returns('1YNY'); + gdprConsentDataStub.returns({ + gdprApplies: false, + consentString: 'consentDataString' + }) + gppConsentDataStub.returns({ + gppString: 'gppConsentDataString', + applicableSections: [1] + }) + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + expect(window.liQHub).to.eql([{ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: { gdpr: { consentString: 'consentDataString', gdprApplies: false }, gpp: { applicableSections: [1], consentString: 'gppConsentDataString' }, usPrivacy: { consentString: '1YNY' } }, + integration: { distributorId: defaultConfigParams.params.distributorId, publisherId: defaultConfigParams.params.publisherId, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }]) + }); + + it('should not fire event again when it is already fired', function() { + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + liveIntentExternalIdSubmodule.decode({}, defaultConfigParams); + + expect(window.liQHub).to.have.length(1) // instead of 2 + }); + + it('should not return a decoded identifier when the unifiedId is not present in the value', function() { + const result = liveIntentExternalIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); + expect(result).to.be.eql({}); + }); + + it('should decode a unifiedId to lipbId and remove it', function() { + const result = liveIntentExternalIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'data'}}); + }); + + it('should decode a nonId to lipbId', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); + }); + + it('should resolve extra attributes', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } + } }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId', 'foo' ], + type: 'resolve' + }) + }); + + it('should decode a uid2 to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode values with uid2 but no nonId', function() { + const result = liveIntentExternalIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); + expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a bidswitch id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a medianet id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a sovrn id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a magnite id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an index id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an openx id to a separate object when present', function () { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode an pubmatic id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a thetradedesk id to a separate object when present', function() { + const provider = 'liveintent.com' + refererInfoStub.returns({domain: provider}) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); + }); + + it('should allow disabling nonId resolution', function() { + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } + } }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: '89899', type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'uid2' ], + type: 'resolve' + }) + }); + + it('should decode a idCookie as fpid if it exists and coppa is false', function() { + coppaConsentDataStub.returns(false) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', idCookie: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'fpid': 'bar'}, 'fpid': {'id': 'bar'}}); + }); + + it('should not decode a idCookie as fpid if it exists and coppa is true', function() { + coppaConsentDataStub.returns(true) + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', idCookie: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo'}}) + }); + + it('should resolve fpid from cookie', function() { + const cookieName = 'testcookie' + liveIntentExternalIdSubmodule.getId({ params: { + ...defaultConfigParams.params, + fpid: { 'strategy': 'cookie', 'name': cookieName }, + requestedAttributesOverrides: { 'fpid': true } } + }).callback(() => {}); + + expect(window.liQHub).to.have.length(2) + expect(window.liQHub[0]).to.eql({ + clientDetails: { name: 'prebid', version: '$prebid.version$' }, + clientRef: {}, + collectSettings: { timeout: DEFAULT_AJAX_TIMEOUT }, + consent: {}, + integration: { distributorId: defaultConfigParams.distributorId, publisherId: PUBLISHER_ID, type: 'custom' }, + partnerCookies: new Set(), + resolveSettings: { identityPartner: 'prebid', timeout: DEFAULT_AJAX_TIMEOUT }, + idCookieSettings: { type: 'provided', key: 'testcookie', source: 'cookie' }, + type: 'register_client' + }) + + const resolveCommand = window.liQHub[1] + + // functions cannot be reasonably compared, remove them + delete resolveCommand.onSuccess[0].callback + delete resolveCommand.onFailure + + expect(resolveCommand).to.eql({ + clientRef: {}, + onSuccess: [{ type: 'callback' }], + requestedAttributes: [ 'nonId', 'idCookie' ], + type: 'resolve' + }) + }); + + it('should decode a sharethrough id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a sonobi id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a vidazoo id to a separate object when present', function() { + const result = liveIntentExternalIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, defaultConfigParams); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); +}); diff --git a/test/spec/modules/liveIntentIdMinimalSystem_spec.js b/test/spec/modules/liveIntentIdMinimalSystem_spec.js index e280d9108a0..a859b3e7995 100644 --- a/test/spec/modules/liveIntentIdMinimalSystem_spec.js +++ b/test/spec/modules/liveIntentIdMinimalSystem_spec.js @@ -1,7 +1,7 @@ import * as utils from 'src/utils.js'; import { gdprDataHandler, uspDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; -import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from '../../../libraries/liveIntentId/idSystem.js'; import * as refererDetection from '../../../src/refererDetection.js'; const PUBLISHER_ID = '89899'; @@ -313,4 +313,19 @@ describe('LiveIntentMinimalId', function() { ); expect(callBackSpy.calledOnce).to.be.true; }); + + it('should decode a sharethrough id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a sonobi id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a vidazoo id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); }); diff --git a/test/spec/modules/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 373142db82e..50f51bd3dc8 100644 --- a/test/spec/modules/liveIntentIdSystem_spec.js +++ b/test/spec/modules/liveIntentIdSystem_spec.js @@ -1,13 +1,15 @@ -import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'modules/liveIntentIdSystem.js'; +import { liveIntentIdSubmodule, reset as resetLiveIntentIdSubmodule, storage } from 'libraries/liveIntentId/idSystem.js'; import * as utils from 'src/utils.js'; -import { gdprDataHandler, uspDataHandler, gppDataHandler } from '../../../src/adapterManager.js'; +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../../../src/adapterManager.js'; import { server } from 'test/mocks/xhr.js'; import * as refererDetection from '../../../src/refererDetection.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; resetLiveIntentIdSubmodule(); liveIntentIdSubmodule.setModuleMode('standard') const PUBLISHER_ID = '89899'; -const defaultConfigParams = { params: {publisherId: PUBLISHER_ID, fireEventDelay: 1} }; +const defaultConfigParams = {publisherId: PUBLISHER_ID, fireEventDelay: 1}; const responseHeader = {'Content-Type': 'application/json'} function requests(...urlRegExps) { @@ -30,6 +32,7 @@ describe('LiveIntentId', function() { let getCookieStub; let getDataFromLocalStorageStub; let imgStub; + let coppaConsentDataStub; let refererInfoStub; beforeEach(function() { @@ -41,6 +44,7 @@ describe('LiveIntentId', function() { uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); gdprConsentDataStub = sinon.stub(gdprDataHandler, 'getConsentData'); gppConsentDataStub = sinon.stub(gppDataHandler, 'getConsentData'); + coppaConsentDataStub = sinon.stub(coppaDataHandler, 'getCoppa'); refererInfoStub = sinon.stub(refererDetection, 'getRefererInfo'); }); @@ -52,11 +56,12 @@ describe('LiveIntentId', function() { uspConsentDataStub.restore(); gdprConsentDataStub.restore(); gppConsentDataStub.restore(); + coppaConsentDataStub.restore(); refererInfoStub.restore(); resetLiveIntentIdSubmodule(); }); - it('should initialize LiveConnect with a privacy string when getId, and include it in the resolution request', function () { + it('should initialize LiveConnect with a privacy string when getId but not send request', function (done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ gdprApplies: true, @@ -67,61 +72,58 @@ describe('LiveIntentId', function() { applicableSections: [1, 2] }) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); - let request = idxRequests()[0]; - expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=1&n3pc=1&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1%2C2.*/); - const response = { - unifiedId: 'a_unified_id', - segments: [123, 234] - } - request.respond( - 200, - responseHeader, - JSON.stringify(response) - ); - expect(callBackSpy.calledOnceWith(response)).to.be.true; + setTimeout(() => { + let requests = idxRequests().concat(rpRequests()); + expect(requests).to.be.empty; + expect(callBackSpy.notCalled).to.be.true; + done(); + }, 300) }); - it('should fire an event when getId', function(done) { + it('should fire an event without privacy setting when getId', function(done) { uspConsentDataStub.returns('1YNY'); gdprConsentDataStub.returns({ - gdprApplies: true, + gdprApplies: false, consentString: 'consentDataString' }) gppConsentDataStub.returns({ gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=1&n3pc=1&n3pct=1&nb=1&gdpr_consent=consentDataString&gpp_s=gppConsentDataString&gpp_as=1.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*&us_privacy=1YNY.*&wpn=prebid.*&gdpr=0.*&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString.*&gpp_as=1.*/); done(); }, 300); }); it('should fire an event when getId and a hash is provided', function(done) { liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/) done(); }, 300); }); it('should initialize LiveConnect and forward the prebid version when decode and emit an event', function(done) { - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.contain('tv=$prebid.version$') + let request = rpRequests()[0]; + expect(request.url).to.contain('tv=$prebid.version$') done(); }, 300); }); it('should initialize LiveConnect with the config params when decode and emit an event', function (done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ url: 'https://dummy.liveintent.com', liCollectConfig: { @@ -131,7 +133,8 @@ describe('LiveIntentId', function() { } }}); setTimeout(() => { - expect(requests(/https:\/\/collector.liveintent.com.*/)[0].url).to.match(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + let request = requests(/https:\/\/collector.liveintent.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); + expect(request.length).to.be.greaterThan(0); done(); }, 300); }); @@ -139,7 +142,8 @@ describe('LiveIntentId', function() { it('should fire an event with the provided distributorId', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111' } }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*did=did-1111.*&wpn=prebid.*/); done(); }, 300); }); @@ -147,7 +151,7 @@ describe('LiveIntentId', function() { it('should fire an event without the provided distributorId when appId is provided', function (done) { liveIntentIdSubmodule.decode({}, { params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }); setTimeout(() => { - const request = rpRequests()[0]; + let request = rpRequests()[0]; expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*aid=a-0001.*&wpn=prebid.*/); expect(request.url).to.not.match(/.*did=*/); done(); @@ -164,42 +168,44 @@ describe('LiveIntentId', function() { gppString: 'gppConsentDataString', applicableSections: [1] }) - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); + let request = rpRequests()[0]; + expect(request.url).to.match(/.*us_privacy=1YNY.*&gdpr=0&gdpr_consent=consentDataString.*&gpp_s=gppConsentDataString&gpp_as=1.*/); done(); }, 300); }); it('should fire an event when decode and a hash is provided', function(done) { liveIntentIdSubmodule.decode({}, { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, emailHash: '58131bc547fb87af94cebdaf3102321f' }}); setTimeout(() => { - expect(rpRequests()[0].url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); + let request = rpRequests()[0]; + expect(request.url).to.match(/https:\/\/rp.liadm.com\/j\?.*e=58131bc547fb87af94cebdaf3102321f.+/); done(); }, 300); }); it('should not return a decoded identifier when the unifiedId is not present in the value', function() { - const result = liveIntentIdSubmodule.decode({ fireEventDelay: 1, additionalData: 'data' }); + const result = liveIntentIdSubmodule.decode({ params: { fireEventDelay: 1, additionalData: 'data' } }); expect(result).to.be.eql({}); }); it('should fire an event when decode', function(done) { - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { - expect(rpRequests()[0].url).to.be.not.null + expect(rpRequests().length).to.be.eq(1); done(); }, 300); }); it('should initialize LiveConnect and send data only once', function(done) { - liveIntentIdSubmodule.getId(defaultConfigParams); - liveIntentIdSubmodule.decode({}, defaultConfigParams); - liveIntentIdSubmodule.getId(defaultConfigParams); - liveIntentIdSubmodule.decode({}, defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); + liveIntentIdSubmodule.decode({}, { params: defaultConfigParams }); setTimeout(() => { expect(rpRequests().length).to.be.eq(1); done(); @@ -209,10 +215,10 @@ describe('LiveIntentId', function() { it('should call the custom URL of the LiveIntent Identity Exchange endpoint', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams.params, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: {...defaultConfigParams, ...{'url': 'https://dummy.liveintent.com/idex'}} }).callback; submoduleCallback(callBackSpy); let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/dummy.liveintent.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -226,7 +232,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111' } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/did-1111/any?did=did-1111&cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/did-1111\/any\?.*did=did-1111.*&cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -240,7 +246,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId({ params: { fireEventDelay: 1, distributorId: 'did-1111', liCollectConfig: { appId: 'a-0001' } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/any?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/any\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 204, responseHeader @@ -252,7 +258,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'url': 'https://dummy.liveintent.com/idex', 'partner': 'rubicon' @@ -260,7 +266,7 @@ describe('LiveIntentId', function() { } }).callback; submoduleCallback(callBackSpy); let request = requests(/https:\/\/dummy.liveintent.com\/idex\/.*/)[0]; - expect(request.url).to.be.eq('https://dummy.liveintent.com/idex/rubicon/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/dummy.liveintent.com\/idex\/rubicon\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -272,10 +278,10 @@ describe('LiveIntentId', function() { it('should call the LiveIntent Identity Exchange endpoint, with no additional query params', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -287,10 +293,10 @@ describe('LiveIntentId', function() { it('should log an error and continue to callback if ajax request errors', function() { getCookieStub.returns(null); let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*/); request.respond( 503, responseHeader, @@ -304,10 +310,11 @@ describe('LiveIntentId', function() { const oldCookie = 'a-xxxx--123e4567-e89b-12d3-a456-426655440000' getCookieStub.withArgs('_lc2_fpi').returns(oldCookie) let callBackSpy = sinon.spy(); - let submoduleCallback = liveIntentIdSubmodule.getId(defaultConfigParams).callback; + let submoduleCallback = liveIntentIdSubmodule.getId({ params: defaultConfigParams }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&resolve=nonId`); + const expected = new RegExp('https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*duid=' + oldCookie + '.*&cd=.localhost.*&resolve=nonId.*'); + expect(request.url).to.match(expected); request.respond( 200, responseHeader, @@ -321,7 +328,7 @@ describe('LiveIntentId', function() { getCookieStub.withArgs('_lc2_fpi').returns(oldCookie); getDataFromLocalStorageStub.withArgs('_thirdPC').returns('third-pc'); const configParams = { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -330,7 +337,8 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?duid=${oldCookie}&cd=.localhost&_thirdPC=third-pc&resolve=nonId`); + const expected = new RegExp('https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*duid=' + oldCookie + '.*&cd=.localhost.*&_thirdPC=third-pc.*&resolve=nonId.*'); + expect(request.url).to.match(expected); request.respond( 200, responseHeader, @@ -343,7 +351,7 @@ describe('LiveIntentId', function() { getCookieStub.returns(null); getDataFromLocalStorageStub.withArgs('_thirdPC').returns({'key': 'value'}); const configParams = { params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ 'identifiersToResolve': ['_thirdPC'] } @@ -352,7 +360,7 @@ describe('LiveIntentId', function() { let submoduleCallback = liveIntentIdSubmodule.getId(configParams).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq('https://idx.liadm.com/idex/prebid/89899?cd=.localhost&_thirdPC=%7B%22key%22%3A%22value%22%7D&resolve=nonId'); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&_thirdPC=%7B%22key%22%3A%22value%22%7D.*&resolve=nonId.*/); request.respond( 200, responseHeader, @@ -361,31 +369,46 @@ describe('LiveIntentId', function() { expect(callBackSpy.calledOnce).to.be.true; }); + it('should include ip4,ip6,userAgent if it\'s present', function(done) { + liveIntentIdSubmodule.getId({ params: { + ...defaultConfigParams, + ipv4: 'foov4', + ipv6: 'foov6', + userAgent: 'boo' + }}); + setTimeout(() => { + let request = rpRequests()[0]; + expect(request.url).to.match(/^https:\/\/rp\.liadm\.com\/j?.*pip=.*&pip6=.*$/) + expect(request.requestHeaders['X-LI-Provided-User-Agent']).to.be.eq('boo') + done(); + }, 300); + }); + it('should send an error when the cookie jar throws an unexpected error', function() { getCookieStub.throws('CookieError', 'A message'); - liveIntentIdSubmodule.getId(defaultConfigParams); + liveIntentIdSubmodule.getId({ params: defaultConfigParams }); expect(imgStub.getCall(0).args[0]).to.match(/.*ae=.+/); }); it('should decode a unifiedId to lipbId and remove it', function() { - const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ unifiedId: 'data' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'data'}}); }); it('should decode a nonId to lipbId', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'data' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'data', 'nonId': 'data'}}); }); it('should resolve extra attributes', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ requestedAttributesOverrides: { 'foo': true, 'bar': false } } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=nonId&resolve=foo`); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=nonId.*&resolve=foo.*/); request.respond( 200, responseHeader, @@ -395,66 +418,66 @@ describe('LiveIntentId', function() { }); it('should decode a uid2 to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', uid2: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'uid2': 'bar'}, 'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode values with uid2 but no nonId', function() { - const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ uid2: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'uid2': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a bidswitch id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', bidswitch: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'bidswitch': 'bar'}, 'bidswitch': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a medianet id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', medianet: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'medianet': 'bar'}, 'medianet': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a sovrn id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sovrn: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sovrn': 'bar'}, 'sovrn': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a magnite id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', magnite: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'magnite': 'bar'}, 'magnite': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an index id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', index: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'index': 'bar'}, 'index': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an openx id to a separate object when present', function () { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', openx: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'openx': 'bar'}, 'openx': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode an pubmatic id to a separate object when present', function() { - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', pubmatic: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'pubmatic': 'bar'}, 'pubmatic': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); }); it('should decode a thetradedesk id to a separate object when present', function() { const provider = 'liveintent.com' refererInfoStub.returns({domain: provider}) - const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, defaultConfigParams); + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', thetradedesk: 'bar' }, { params: defaultConfigParams }); expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'tdid': 'bar'}, 'tdid': {'id': 'bar', 'ext': {'rtiPartner': 'TDID', 'provider': provider}}}); }); it('should allow disabling nonId resolution', function() { let callBackSpy = sinon.spy(); let submoduleCallback = liveIntentIdSubmodule.getId({ params: { - ...defaultConfigParams.params, + ...defaultConfigParams, ...{ requestedAttributesOverrides: { 'nonId': false, 'uid2': true } } } }).callback; submoduleCallback(callBackSpy); let request = idxRequests()[0]; - expect(request.url).to.be.eq(`https://idx.liadm.com/idex/prebid/89899?cd=.localhost&resolve=uid2`); + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&resolve=uid2.*/); request.respond( 200, responseHeader, @@ -462,4 +485,434 @@ describe('LiveIntentId', function() { ); expect(callBackSpy.calledOnce).to.be.true; }); -}); + + it('should decode a idCookie as fpid if it exists and coppa is false', function() { + coppaConsentDataStub.returns(false) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'fpid': 'bar'}, 'fpid': {'id': 'bar'}}) + }); + + it('should not decode a idCookie as fpid if it exists and coppa is true', function() { + coppaConsentDataStub.returns(true) + const result = liveIntentIdSubmodule.decode({nonId: 'foo', idCookie: 'bar'}, { params: defaultConfigParams }) + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo'}}) + }); + + it('should resolve fpid from cookie', async function() { + const expectedValue = 'someValue' + const cookieName = 'testcookie' + getCookieStub.withArgs(cookieName).returns(expectedValue) + const config = { params: { + ...defaultConfigParams, + fpid: { 'strategy': 'cookie', 'name': cookieName }, + requestedAttributesOverrides: { 'fpid': true } } + } + const submoduleCallback = liveIntentIdSubmodule.getId(config).callback; + const decodedResult = new Promise(resolve => { + submoduleCallback((x) => resolve(liveIntentIdSubmodule.decode(x, config))); + }); + const request = idxRequests()[0]; + expect(request.url).to.match(/https:\/\/idx.liadm.com\/idex\/prebid\/89899\?.*cd=.localhost.*&ic=someValue.*&resolve=nonId.*/); + request.respond( + 200, + responseHeader, + JSON.stringify({}) + ); + + const result = await decodedResult + expect(result).to.be.eql({ + lipb: { 'fpid': expectedValue }, + fpid: { id: expectedValue } + }); + }); + + it('should decode a sharethrough id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sharethrough: 'bar' }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sharethrough': 'bar'}, 'sharethrough': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a sonobi id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', sonobi: 'bar' }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'sonobi': 'bar'}, 'sonobi': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + it('should decode a vidazoo id to a separate object when present', function() { + const result = liveIntentIdSubmodule.decode({ nonId: 'foo', vidazoo: 'bar' }, { params: defaultConfigParams }); + expect(result).to.eql({'lipb': {'lipbid': 'foo', 'nonId': 'foo', 'vidazoo': 'bar'}, 'vidazoo': {'id': 'bar', 'ext': {'provider': 'liveintent.com'}}}); + }); + + describe('eid', () => { + before(() => { + attachIdSystem(liveIntentIdSubmodule); + }); + it('liveIntentId; getValue call and ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value', + segments: ['s1', 's2'] + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 3}], + ext: {segments: ['s1', 's2']} + }); + }); + it('fpid; getValue call', function() { + const userId = { + fpid: { + id: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'fpid.liveintent.com', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + it('bidswitch', function() { + const userId = { + bidswitch: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('bidswitch with ext', function() { + const userId = { + bidswitch: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'bidswitch.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('medianet', function() { + const userId = { + medianet: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('medianet with ext', function() { + const userId = { + medianet: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'media.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('sovrn', function() { + const userId = { + sovrn: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('sovrn with ext', function() { + const userId = { + sovrn: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sovrn.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('magnite', function() { + const userId = { + magnite: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('magnite with ext', function() { + const userId = { + magnite: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'rubiconproject.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + it('index', function() { + const userId = { + index: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.indexexchange.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('index with ext', function() { + const userId = { + index: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.indexexchange.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('openx', function () { + const userId = { + openx: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'openx.net', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('openx with ext', function () { + const userId = { + openx: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'openx.net', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('pubmatic', function() { + const userId = { + pubmatic: {'id': 'sample_id'} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubmatic.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('pubmatic with ext', function() { + const userId = { + pubmatic: {'id': 'sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubmatic.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('liveIntentId; getValue call and NO ext', function() { + const userId = { + lipb: { + lipbid: 'some-random-id-value' + } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.com', + uids: [{id: 'some-random-id-value', atype: 3}] + }); + }); + + it('sharethrough', function () { + const userId = { + sharethrough: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharethrough.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('sharethrough with ext', function () { + const userId = { + sharethrough: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'sharethrough.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('sonobi', function () { + const userId = { + sonobi: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sonobi.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('sonobi with ext', function () { + const userId = { + sonobi: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.sonobi.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + + it('vidazoo', function () { + const userId = { + vidazoo: { 'id': 'sample_id' } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.vidazoo.com', + uids: [{ + id: 'sample_id', + atype: 3 + }] + }); + }); + + it('vidazoo with ext', function () { + const userId = { + vidazoo: { 'id': 'sample_id', 'ext': { 'provider': 'some.provider.com' } } + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'liveintent.vidazoo.com', + uids: [{ + id: 'sample_id', + atype: 3, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); + }) +}) diff --git a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js index f82cc4c4f52..1e5f089ca53 100644 --- a/test/spec/modules/livewrappedAnalyticsAdapter_spec.js +++ b/test/spec/modules/livewrappedAnalyticsAdapter_spec.js @@ -341,7 +341,6 @@ describe('Livewrapped analytics adapter', function () { }); it('should build a batched message from prebid events', function () { - sandbox.stub(utils, 'getWindowTop').returns({}); performStandardAuction(); clock.tick(BID_WON_TIMEOUT + 1000); @@ -403,20 +402,6 @@ describe('Livewrapped analytics adapter', function () { expect(message.timeouts[0].adUnit).to.equal('panorama_d_1'); }); - it('should detect adblocker recovered request', function () { - sandbox.stub(utils, 'getWindowTop').returns({ I12C: { Morph: 1 } }); - performStandardAuction(); - - clock.tick(BID_WON_TIMEOUT + 1000); - - expect(server.requests.length).to.equal(1); - let request = server.requests[0]; - - let message = JSON.parse(request.requestBody); - - expect(message.rcv).to.equal(true); - }); - it('should forward GDPR data', function () { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, { @@ -623,4 +608,40 @@ describe('Livewrapped analytics adapter', function () { expect(request.url).to.equal('https://whitelabeled.com/analytics/10'); }); }); + + describe('when given extended options', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'livewrapped', + adapter: livewrappedAnalyticsAdapter + }); + + beforeEach(function () { + adapterManager.enableAnalytics({ + provider: 'livewrapped', + options: { + publisherId: 'CC411485-42BC-4F92-8389-42C503EE38D7', + ext: { + testparam: 123 + } + } + }); + }); + + afterEach(function () { + livewrappedAnalyticsAdapter.disableAnalytics(); + }); + + it('should forward the extended options', function () { + performStandardAuction(); + + clock.tick(BID_WON_TIMEOUT + 1000); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + let message = JSON.parse(request.requestBody); + + expect(message.ext).to.not.equal(null); + expect(message.ext.testparam).to.equal(123); + }); + }); }); diff --git a/test/spec/modules/lkqdBidAdapter_spec.js b/test/spec/modules/lkqdBidAdapter_spec.js index 4ff69ce5e2a..1e05b9deeb3 100644 --- a/test/spec/modules/lkqdBidAdapter_spec.js +++ b/test/spec/modules/lkqdBidAdapter_spec.js @@ -46,12 +46,12 @@ describe('lkqdBidAdapter', () => { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { wrong: 'missing zone id' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/lm_kiviadsBidAdapter_spec.js b/test/spec/modules/lm_kiviadsBidAdapter_spec.js index 68ac73289cd..645dd756c19 100644 --- a/test/spec/modules/lm_kiviadsBidAdapter_spec.js +++ b/test/spec/modules/lm_kiviadsBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect} from 'chai'; import {config} from 'src/config.js'; -import {spec, getBidFloor} from 'modules/lm_kiviadsBidAdapter.js'; +import {spec} from 'modules/lm_kiviadsBidAdapter.js'; import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.kiviads.live'; @@ -101,7 +102,6 @@ describe('lm_kiviadsBidAdapter', () => { expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ @@ -214,14 +214,6 @@ describe('lm_kiviadsBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ diff --git a/test/spec/modules/lmpIdSystem_spec.js b/test/spec/modules/lmpIdSystem_spec.js index 37c7351f143..28f8ba0697d 100644 --- a/test/spec/modules/lmpIdSystem_spec.js +++ b/test/spec/modules/lmpIdSystem_spec.js @@ -1,9 +1,10 @@ import { expect } from 'chai'; import { find } from 'src/polyfill.js'; import { config } from 'src/config.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; +import { init, startAuctionHook, setSubmoduleRegistry } from 'modules/userId/index.js'; import { storage, lmpIdSubmodule } from 'modules/lmpIdSystem.js'; import { mockGdprConsent } from '../../helpers/consentData.js'; +import 'src/prebid.js'; function getConfigMock() { return { @@ -99,10 +100,15 @@ describe('LMPID System', () => { afterEach(() => { sandbox.restore(); + config.resetConfig(); }); + after(() => { + init(config); + }) + it('when a stored LMPID exists it is added to bids', (done) => { - requestBidsHook(() => { + startAuctionHook(() => { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.lmpid'); diff --git a/test/spec/modules/loganBidAdapter_spec.js b/test/spec/modules/loganBidAdapter_spec.js index a9859bbd4ae..f51f22580e2 100644 --- a/test/spec/modules/loganBidAdapter_spec.js +++ b/test/spec/modules/loganBidAdapter_spec.js @@ -79,7 +79,7 @@ describe('LoganBidAdapter', function () { expect(data).to.be.an('object'); let placement = data['placements'][0]; expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); + expect(placement).to.have.keys('placementId', 'bidId', 'adFormat', 'wPlayer', 'hPlayer', 'schain', 'minduration', 'maxduration', 'mimes', 'protocols', 'startdelay', 'placement', 'plcmt', 'skip', 'skipafter', 'minbitrate', 'maxbitrate', 'delivery', 'playbackmethod', 'api', 'linearity', 'bidfloor'); expect(placement.adFormat).to.equal(VIDEO); expect(placement.wPlayer).to.equal(playerSize[0]); expect(placement.hPlayer).to.equal(playerSize[1]); diff --git a/test/spec/modules/logicadBidAdapter_spec.js b/test/spec/modules/logicadBidAdapter_spec.js index 12e8ca31cbb..5c86ffc9325 100644 --- a/test/spec/modules/logicadBidAdapter_spec.js +++ b/test/spec/modules/logicadBidAdapter_spec.js @@ -182,7 +182,9 @@ describe('LogicadAdapter', function () { stack: [] }, auctionStart: 1563337198010, - fledgeEnabled: true + paapi: { + enabled: true + } }; const serverResponse = { body: { @@ -388,8 +390,8 @@ describe('LogicadAdapter', function () { const paapiRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; const paapiInterpretedResponse = spec.interpretResponse(paapiServerResponse, paapiRequest); expect(paapiInterpretedResponse).to.have.property('bids'); - expect(paapiInterpretedResponse).to.have.property('fledgeAuctionConfigs'); - expect(paapiInterpretedResponse.fledgeAuctionConfigs[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); + expect(paapiInterpretedResponse).to.have.property('paapi'); + expect(paapiInterpretedResponse.paapi[0]).to.deep.equal(paapiServerResponse.body.ext.fledgeAuctionConfigs[0]); // native const nativeRequest = spec.buildRequests(nativeBidRequests, bidderRequest)[0]; diff --git a/test/spec/modules/lotamePanoramaIdSystem_spec.js b/test/spec/modules/lotamePanoramaIdSystem_spec.js index ea538db08e1..27efef9df50 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -2,10 +2,11 @@ import { lotamePanoramaIdSubmodule, storage, } from 'modules/lotamePanoramaIdSystem.js'; -import { uspDataHandler } from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; import { server } from 'test/mocks/xhr.js'; import sinon from 'sinon'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; const responseHeader = { 'Content-Type': 'application/json' }; @@ -17,11 +18,9 @@ describe('LotameId', function() { let setLocalStorageStub; let removeFromLocalStorageStub; let timeStampStub; - let uspConsentDataStub; let requestHost; const nowTimestamp = new Date().getTime(); - beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); getCookieStub = sinon.stub(storage, 'getCookie'); @@ -33,7 +32,6 @@ describe('LotameId', function() { 'removeDataFromLocalStorage' ); timeStampStub = sinon.stub(utils, 'timestamp').returns(nowTimestamp); - uspConsentDataStub = sinon.stub(uspDataHandler, 'getConsentData'); if (navigator.userAgent && navigator.userAgent.indexOf('Safari') != -1 && navigator.userAgent.indexOf('Chrome') == -1) { requestHost = 'https://c.ltmsphrcl.net/id'; } else { @@ -49,7 +47,6 @@ describe('LotameId', function() { setLocalStorageStub.restore(); removeFromLocalStorageStub.restore(); timeStampStub.restore(); - uspConsentDataStub.restore(); }); describe('caching initial data received from the remote server', function () { @@ -450,70 +447,6 @@ describe('LotameId', function() { }); }); - describe('when gdpr applies and falls back to eupubconsent cookie', function () { - let request; - let callBackSpy = sinon.spy(); - let consentData = { - gdprApplies: true, - consentString: undefined - }; - - beforeEach(function () { - getCookieStub - .withArgs('eupubconsent-v2') - .returns('consentGiven'); - - let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; - submoduleCallback(callBackSpy); - - // the contents of the response don't matter for this - request = server.requests[0]; - request.respond(200, responseHeader, ''); - }); - - it('should call the remote server when getId is called', function () { - expect(callBackSpy.calledOnce).to.be.true; - }); - - it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq( - `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` - ); - }); - }); - - describe('when gdpr applies and falls back to euconsent cookie', function () { - let request; - let callBackSpy = sinon.spy(); - let consentData = { - gdprApplies: true, - consentString: undefined - }; - - beforeEach(function () { - getCookieStub - .withArgs('euconsent-v2') - .returns('consentGiven'); - - let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; - submoduleCallback(callBackSpy); - - // the contents of the response don't matter for this - request = server.requests[0]; - request.respond(200, responseHeader, ''); - }); - - it('should call the remote server when getId is called', function () { - expect(callBackSpy.calledOnce).to.be.true; - }); - - it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq( - `${requestHost}?gdpr_applies=true&gdpr_consent=consentGiven` - ); - }); - }); - describe('when gdpr applies but no consent string is available', function () { let request; let callBackSpy = sinon.spy(); @@ -542,64 +475,6 @@ describe('LotameId', function() { }); }); - describe('when no consentData and falls back to eupubconsent cookie', function () { - let request; - let callBackSpy = sinon.spy(); - let consentData; - - beforeEach(function () { - getCookieStub - .withArgs('eupubconsent-v2') - .returns('consentGiven'); - - let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; - submoduleCallback(callBackSpy); - - // the contents of the response don't matter for this - request = server.requests[0]; - request.respond(200, responseHeader, ''); - }); - - it('should call the remote server when getId is called', function () { - expect(callBackSpy.calledOnce).to.be.true; - }); - - it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq( - `${requestHost}?gdpr_consent=consentGiven` - ); - }); - }); - - describe('when no consentData and falls back to euconsent cookie', function () { - let request; - let callBackSpy = sinon.spy(); - let consentData; - - beforeEach(function () { - getCookieStub - .withArgs('euconsent-v2') - .returns('consentGiven'); - - let submoduleCallback = lotamePanoramaIdSubmodule.getId({}, consentData).callback; - submoduleCallback(callBackSpy); - - // the contents of the response don't matter for this - request = server.requests[0]; - request.respond(200, responseHeader, ''); - }); - - it('should call the remote server when getId is called', function () { - expect(callBackSpy.calledOnce).to.be.true; - }); - - it('should pass the gdpr consent string back', function() { - expect(request.url).to.be.eq( - `${requestHost}?gdpr_consent=consentGiven` - ); - }); - }); - describe('when no consentData and no cookies', function () { let request; let callBackSpy = sinon.spy(); @@ -808,7 +683,6 @@ describe('LotameId', function() { let callBackSpy = sinon.spy(); beforeEach(function () { - uspConsentDataStub.returns('1NNN'); let submoduleCallback = lotamePanoramaIdSubmodule.getId( { params: { @@ -839,12 +713,6 @@ describe('LotameId', function() { expect(callBackSpy.calledOnce).to.be.true; }); - it('should pass the usp consent string and client id back', function () { - expect(request.url).to.be.eq( - `${requestHost}?gdpr_applies=false&us_privacy=1NNN&c=1234` - ); - }); - it('should NOT set an expiry for the client', function () { sinon.assert.neverCalledWith( setCookieStub, @@ -958,4 +826,20 @@ describe('LotameId', function() { }); }); }); + describe('eid', () => { + before(() => { + attachIdSystem(lotamePanoramaIdSubmodule); + }); + it('lotamePanoramaId', function () { + const userId = { + lotamePanoramaId: 'some-random-id-value', + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'crwdcntrl.net', + uids: [{ id: 'some-random-id-value', atype: 1 }], + }); + }); + }) }); diff --git a/test/spec/modules/loyalBidAdapter_spec .js b/test/spec/modules/loyalBidAdapter_spec.js similarity index 77% rename from test/spec/modules/loyalBidAdapter_spec .js rename to test/spec/modules/loyalBidAdapter_spec.js index 28e87fc7047..fbfcdcd0742 100644 --- a/test/spec/modules/loyalBidAdapter_spec .js +++ b/test/spec/modules/loyalBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/loyalBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'loyal' +const bidder = 'loyal'; describe('LoyalBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('LoyalBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -74,10 +87,19 @@ describe('LoyalBidAdapter', function () { const bidderRequest = { uspConsent: '1---', gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -114,6 +136,7 @@ describe('LoyalBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -147,7 +170,56 @@ describe('LoyalBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); - expect(placement.eids).to.exist.and.to.be.an('array'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -174,6 +246,9 @@ describe('LoyalBidAdapter', function () { let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +263,38 @@ describe('LoyalBidAdapter', function () { 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); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { diff --git a/test/spec/modules/luceadBidAdapter_spec.js b/test/spec/modules/luceadBidAdapter_spec.js old mode 100644 new mode 100755 index 72bc7cc2d6e..6f61071b653 --- a/test/spec/modules/luceadBidAdapter_spec.js +++ b/test/spec/modules/luceadBidAdapter_spec.js @@ -28,6 +28,7 @@ describe('Lucead Adapter', () => { bidder: 'lucead', params: { placementId: '1', + region: 'eu', }, }; }); @@ -39,7 +40,10 @@ describe('Lucead Adapter', () => { describe('onBidWon', function () { let sandbox; - const bid = { foo: 'bar', creativeId: 'ssp:improve' }; + const bids = [ + { foo: 'bar', creativeId: 'ssp:improve' }, + { foo: 'bar', creativeId: '123:456' }, + ]; beforeEach(function () { sandbox = sinon.sandbox.create(); @@ -47,8 +51,11 @@ describe('Lucead Adapter', () => { it('should trigger impression pixel', function () { sandbox.spy(ajax, 'fetch'); - spec.onBidWon(bid); - expect(ajax.fetch.args[0][0]).to.match(/report\/impression$/); + + for (const bid of bids) { + spec.onBidWon(bid); + expect(ajax?.fetch?.args[0][0]).to.match(/report\/impression$/); + } }); afterEach(function () { @@ -77,49 +84,62 @@ describe('Lucead Adapter', () => { it('should have a post method', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].method).to.equal('POST'); + expect(request.method).to.equal('POST'); }); it('should contains a request id equals to the bid id', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(JSON.parse(request[0].data).bid_id).to.equal(bidRequests[0].bidId); + expect(JSON.parse(request.data).bid_requests[0].bid_id).to.equal(bidRequests[0].bidId); }); - it('should have an url that contains sub keyword', function () { + it('should have an url that contains sra keyword', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request[0].url).to.match(/sub/); + expect(request.url).to.contain('/prebid/sra'); }); }); describe('interpretResponse', function () { - const serverResponse = { - body: { - 'bid_id': '2daf899fbe4c52', - 'request_id': '13aaa3df18bfe4', - 'ad': 'Ad', - 'ad_id': '3890677904', - 'cpm': 3.02, - 'currency': 'USD', - 'time': 1707257712095, - 'size': {'width': 300, 'height': 250}, - } + const serverResponseBody = { + 'request_id': '17548f887fb722', + 'bids': [ + { + 'bid_id': '2d663fdd390b49', + 'ad': '\u003chtml lang="en"\u003e\u003cbody style="margin:0;background-color:#FFF"\u003e\u003ciframe src="urn:uuid:fb81a0f9-b83a-4f27-8676-26760d090f1c" style="width:300px;height:250px;border:none" seamless \u003e\u003c/iframe\u003e\u003c/body\u003e\u003c/html\u003e', + 'size': { + 'width': 300, + 'height': 250 + }, + 'ad_id': '1', + 'ig_id': '1', + 'cpm': 1, + 'currency': 'EUR', + 'time': 0, + 'ssp': '', + 'placement_id': '1', + 'is_pa': true + } + ] }; - const bidRequest = {data: JSON.stringify({ - 'request_id': '13aaa3df18bfe4', - 'domain': '7cdb-2a02-8429-e4a0-1701-bc69-d51c-86e-b279.ngrok-free.app', - 'bid_id': '2daf899fbe4c52', - 'sizes': [[300, 250]], - 'media_types': {'banner': {'sizes': [[300, 250]]}}, - 'fledge_enabled': true, - 'enable_contextual': true, - 'enable_pa': true, - 'params': {'placementId': '1'}, - })}; + const serverResponse = {body: serverResponseBody}; + + const bidRequest = { + data: JSON.stringify({ + 'request_id': '17548f887fb722', + 'domain': 'lucead.com', + 'bid_requests': [{ + 'bid_id': '2d663fdd390b49', + 'sizes': [[300, 250], [300, 150]], + 'media_types': {'banner': {'sizes': [[300, 250], [300, 150]]}}, + 'placement_id': '1' + }], + }), + }; it('should get correct bid response', function () { const result = spec.interpretResponse(serverResponse, bidRequest); + // noinspection JSCheckFunctionSignatures expect(Object.keys(result.bids[0])).to.have.members([ 'requestId', 'cpm', @@ -135,7 +155,7 @@ describe('Lucead Adapter', () => { }); it('should return bid empty response', function () { - const serverResponse = {body: {cpm: 0}}; + const serverResponse = {body: {bids: [{cpm: 0}]}}; const bidRequest = {data: '{}'}; const result = spec.interpretResponse(serverResponse, bidRequest); expect(result.bids[0].ad).to.be.equal(''); @@ -154,18 +174,11 @@ describe('Lucead Adapter', () => { expect(Object.keys(result.bids[0].meta)).to.include.members(['advertiserDomains']); }); - it('should support disabled contextual bids', function () { - const serverResponseWithDisabledContectual = deepClone(serverResponse); - serverResponseWithDisabledContectual.body.enable_contextual = false; - const result = spec.interpretResponse(serverResponseWithDisabledContectual, bidRequest); - expect(result.bids).to.be.null; - }); - - it('should support disabled Protected Audience', function () { - const serverResponseWithEnablePaFalse = deepClone(serverResponse); - serverResponseWithEnablePaFalse.body.enable_pa = false; - const result = spec.interpretResponse(serverResponseWithEnablePaFalse, bidRequest); - expect(result.fledgeAuctionConfigs).to.be.undefined; + it('should support enable_pa = false', function () { + serverResponse.body.enable_pa = false; + const result = spec.interpretResponse(serverResponse, bidRequest); + expect(result).to.be.an('array'); + expect(result[0].cpm).to.be.greaterThan(0); }); }); }); diff --git a/test/spec/modules/lunamediahbBidAdapter_spec.js b/test/spec/modules/lunamediahbBidAdapter_spec.js index 181b6e75fe7..8ae3220504d 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -1,145 +1,253 @@ import {expect} from 'chai'; import {spec} from '../../../modules/lunamediahbBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'lunamediahb'; describe('LunamediaHBBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'lunamediahb', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://balancer.lmgssp.com/?c=o&m=multi'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain'); - expect(placement.placementId).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'wPlayer', 'hPlayer', 'schain', 'videoContext'); - expect(placement.traffic).to.equal(VIDEO); - expect(placement.wPlayer).to.equal(playerSize[0]); - expect(placement.hPlayer).to.equal(playerSize[1]); - }); + it('Returns valid placements', function () { + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); - it('Returns valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); } - }; - - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + switch (placement.adFormat) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); 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); + 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; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([]); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { @@ -154,23 +262,28 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); expect(bannerResponses).to.be.an('array').that.is.not.empty; let dataItem = bannerResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'width', 'height', 'ad', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); - expect(dataItem.requestId).to.equal('23fhj33i987f'); - expect(dataItem.cpm).to.equal(0.4); - expect(dataItem.width).to.equal(300); - expect(dataItem.height).to.equal(250); - expect(dataItem.ad).to.equal('Test'); - expect(dataItem.ttl).to.equal(120); - expect(dataItem.creativeId).to.equal('2'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); + expect(dataItem.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -183,7 +296,11 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -191,7 +308,7 @@ describe('LunamediaHBBidAdapter', function () { let dataItem = videoResponses[0]; expect(dataItem).to.have.all.keys('requestId', 'cpm', 'vastUrl', 'ttl', 'creativeId', - 'netRevenue', 'currency', 'dealId', 'mediaType'); + 'netRevenue', 'currency', 'dealId', 'mediaType', 'meta'); expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.5); expect(dataItem.vastUrl).to.equal('test.com'); @@ -199,6 +316,7 @@ describe('LunamediaHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -216,13 +334,17 @@ describe('LunamediaHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); expect(nativeResponses).to.be.an('array').that.is.not.empty; let dataItem = nativeResponses[0]; - expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native'); + expect(dataItem).to.have.keys('requestId', 'cpm', 'ttl', 'creativeId', 'netRevenue', 'currency', 'mediaType', 'native', 'meta'); expect(dataItem.native).to.have.keys('clickUrl', 'impressionTrackers', 'title', 'image') expect(dataItem.requestId).to.equal('23fhj33i987f'); expect(dataItem.cpm).to.equal(0.4); @@ -235,6 +357,7 @@ describe('LunamediaHBBidAdapter', 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 = { @@ -326,5 +449,17 @@ describe('LunamediaHBBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cookie.lmgssp.com/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cookie.lmgssp.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/luponmediaBidAdapter_spec.js b/test/spec/modules/luponmediaBidAdapter_spec.js index 1441abc0fe8..664c888b45e 100755 --- a/test/spec/modules/luponmediaBidAdapter_spec.js +++ b/test/spec/modules/luponmediaBidAdapter_spec.js @@ -21,12 +21,12 @@ describe('luponmediaBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'siteId': 12345 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -385,49 +385,4 @@ describe('luponmediaBidAdapter', function () { expect(checkSchain).to.equal(false); }); }); - - describe('onBidWon', function () { - const bidWonEvent = { - 'bidderCode': 'luponmedia', - 'width': 300, - 'height': 250, - 'statusMessage': 'Bid available', - 'adId': '105bbf8c54453ff', - 'requestId': '934b8752185955', - 'mediaType': 'banner', - 'source': 'client', - 'cpm': 0.364, - 'creativeId': '443801010', - 'currency': 'USD', - 'netRevenue': false, - 'ttl': 300, - 'referrer': '', - 'ad': '', - 'auctionId': '926a8ea3-3dd4-4bf2-95ab-c85c2ce7e99b', - 'responseTimestamp': 1598527728026, - 'requestTimestamp': 1598527727629, - 'bidder': 'luponmedia', - 'adUnitCode': 'div-gpt-ad-1533155193780-5', - 'timeToRespond': 397, - 'size': '300x250', - 'status': 'rendered' - }; - - let ajaxStub; - - beforeEach(() => { - ajaxStub = sinon.stub(spec, 'sendWinningsToServer') - }) - - afterEach(() => { - ajaxStub.restore() - }) - - it('calls luponmedia\'s callback endpoint', () => { - const result = spec.onBidWon(bidWonEvent); - expect(result).to.equal(undefined); - expect(ajaxStub.calledOnce).to.equal(true); - expect(ajaxStub.firstCall.args[0]).to.deep.equal(JSON.stringify(bidWonEvent)); - }); - }); }); diff --git a/test/spec/modules/madvertiseBidAdapter_spec.js b/test/spec/modules/madvertiseBidAdapter_spec.js index 466d30acdd3..8128bcc2d42 100644 --- a/test/spec/modules/madvertiseBidAdapter_spec.js +++ b/test/spec/modules/madvertiseBidAdapter_spec.js @@ -118,7 +118,7 @@ describe('madvertise adapater', () => { 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][format]=iab`); expect(req[0].url).to.contain(`&consent[0][value]=CO_5mtSPHOmEIAsAkBFRBOCsAP_AAH_AAAqIHQgB7SrERyNAYWB5gusAKYlfQAQCA2AABAYdASgJQQBAMJYEkGAIuAnAACAKAAAEIHQAAAAlCCmABAEAAIABBSGMAQgABZAAIiAEEAATAABACAABGYCSCAIQjIAAAAEAgEKEAAoAQGBAAAEgBABAAAogACADAgXmACIKkQBAkBAYAkAYQAogAhAAAAAIAAAAAAAKAABAAAghAAQQAAAAAAAAAgAAAAABAAAAAAAAQAAAAAAAAABAAgAAAAAAAAAIAAAAAAAAAAAAAAAABAAAAAAAAAAAQCAKCgBgEQALgAqkJADAIgAXABVIaACAAERABAACKgAgABA`) }); diff --git a/test/spec/modules/magniteAnalyticsAdapter_spec.js b/test/spec/modules/magniteAnalyticsAdapter_spec.js index 731b4ab1682..a5644ffc6eb 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -26,6 +26,7 @@ const { BID_TIMEOUT, BILLABLE_EVENT, SEAT_NON_BID, + PBS_ANALYTICS, BID_REJECTED } = EVENTS; @@ -639,6 +640,45 @@ describe('magnite analytics adapter', function () { ]); }); + it('should pass along atag data', function () { + const PBS_ANALYTICS_EVENT = { + 'auctionId': '99785e47-a7c8-4c8a-ae05-ef1c717a4b4d', + atag: [{ + 'stage': 'processed-auction-request', + 'module': 'mgni-timeout-optimization', + 'analyticstags': [{ + activities: [{ + name: 'optimize-tmax', + status: 'success', + results: [{ + status: 'success', + values: { + 'scenario': 'a', + 'rule': 'b', + 'tmax': 3 + } + }] + }] + }] + }] + } + + events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); + events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); + events.emit(BID_RESPONSE, MOCK.BID_RESPONSE); + events.emit(PBS_ANALYTICS, PBS_ANALYTICS_EVENT) + events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); + events.emit(AUCTION_END, MOCK.AUCTION_END); + clock.tick(rubiConf.analyticsBatchTimeout + 1000); + + let message = JSON.parse(server.requests[0].requestBody); + expect(message.auctions[0].experiments[0]).to.deep.equal({ + name: 'a', + rule: 'b', + value: 3 + }); + }); + it('should pass along user ids', function () { let auctionInit = utils.deepClone(MOCK.AUCTION_INIT); auctionInit.bidderRequests[0].bids[0].userId = { @@ -1758,16 +1798,32 @@ describe('magnite analytics adapter', function () { }); }); describe('cookieless', () => { - beforeEach(() => { - magniteAdapter.enableAnalytics({ - options: { - cookieles: undefined - } - }); - }) afterEach(() => { magniteAdapter.disableAnalytics(); }) + it('should not add cookieless and preserve original rule name', () => { + // Set the confs + config.setConfig({ + rubicon: { + wrapperName: '1001_general', + wrapperFamily: 'general', + rule_name: 'desktop-magnite.com', + } + }); + performStandardAuction(); + + expect(server.requests.length).to.equal(1); + let request = server.requests[0]; + + expect(request.url).to.match(/\/\/localhost:9999\/event/); + + let message = JSON.parse(request.requestBody); + expect(message.wrapper).to.deep.equal({ + name: '1001_general', + family: 'general', + rule: 'desktop-magnite.com', + }); + }) it('should add sufix _cookieless to the wrapper.rule if ortb2.device.ext.cdep start with "treatment" or "control_2"', () => { // Set the confs config.setConfig({ @@ -2246,7 +2302,7 @@ describe('magnite analytics adapter', function () { const runNonBidAuction = () => { events.emit(AUCTION_INIT, MOCK.AUCTION_INIT); events.emit(BID_REQUESTED, MOCK.BID_REQUESTED); - events.emit(SEAT_NON_BID, seatnonbid) + events.emit(PBS_ANALYTICS, seatnonbid) events.emit(BIDDER_DONE, MOCK.BIDDER_DONE); events.emit(AUCTION_END, MOCK.AUCTION_END); clock.tick(rubiConf.analyticsBatchTimeout + 1000); diff --git a/test/spec/modules/mantisBidAdapter_spec.js b/test/spec/modules/mantisBidAdapter_spec.js index 579f41e620d..f0f453d32a0 100644 --- a/test/spec/modules/mantisBidAdapter_spec.js +++ b/test/spec/modules/mantisBidAdapter_spec.js @@ -35,10 +35,10 @@ describe('MantisAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/mathildeadsBidAdapter_spec.js b/test/spec/modules/mathildeadsBidAdapter_spec.js index 107906ec83d..cdd1ffbc4bd 100644 --- a/test/spec/modules/mathildeadsBidAdapter_spec.js +++ b/test/spec/modules/mathildeadsBidAdapter_spec.js @@ -3,21 +3,32 @@ import { spec } from '../../../modules/mathildeadsBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'mathildeads' +const bidder = 'mathildeads'; describe('MathildeAdsBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), - bidder: bidder, + bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('MathildeAdsBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('MathildeAdsBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -66,14 +79,27 @@ describe('MathildeAdsBidAdapter', function () { sizes: [[300, 250]] } }, - params: {} + params: { + + } } const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -110,6 +136,7 @@ describe('MathildeAdsBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -127,7 +154,7 @@ describe('MathildeAdsBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -142,6 +169,8 @@ describe('MathildeAdsBidAdapter', function () { expect(placement.bidId).to.be.a('string'); expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -167,8 +196,10 @@ describe('MathildeAdsBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -183,12 +214,38 @@ describe('MathildeAdsBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -367,6 +424,7 @@ describe('MathildeAdsBidAdapter', function () { expect(serverResponses).to.be.an('array').that.is.empty; }); }); + describe('getUserSyncs', function() { it('Should return array of objects with proper sync config , include GDPR', function() { const syncData = spec.getUserSyncs({}, {}, { @@ -391,5 +449,17 @@ describe('MathildeAdsBidAdapter', function () { 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') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/mediaConsortiumBidAdapter_spec.js b/test/spec/modules/mediaConsortiumBidAdapter_spec.js new file mode 100644 index 00000000000..6a7ac6f5741 --- /dev/null +++ b/test/spec/modules/mediaConsortiumBidAdapter_spec.js @@ -0,0 +1,411 @@ +import { expect } from 'chai'; +import { spec, OPTIMIZATIONS_STORAGE_KEY, getOptimizationsFromLocalStorage } from 'modules/mediaConsortiumBidAdapter.js'; + +const BANNER_BID = { + adUnitCode: 'dfp_ban_atf', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + } + } +} + +const VIDEO_BID = { + adUnitCode: 'video', + bidId: '2f0d9715f60be8', + mediaTypes: { + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + } +} + +const VIDEO_BID_WITH_MISSING_CONTEXT = { + adUnitCode: 'video', + bidId: '2f0d9715f60be8', + mediaTypes: { + video: { + playerSize: [ + [300, 250] + ] + } + } +} + +const MULTI_MEDIATYPES_BID = { + adUnitCode: 'multi_type', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [ + [300, 250] + ], + context: 'outstream' + } + } +} + +const MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT = { + adUnitCode: 'multi_type', + bidId: '2f0d9715f60be8', + mediaTypes: { + banner: { + sizes: [ + [300, 250] + ] + }, + video: { + playerSize: [ + [300, 250] + ], + context: 'instream' + } + } +} + +describe('Media Consortium Bid Adapter', function () { + before(function () { + // The local storage variable is not cleaned in some other test so we need to do it ourselves here + localStorage.removeItem('ope_fpid') + }) + + beforeEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = { + mediaConsortium: { + storageAllowed: true + } + } + }) + + afterEach(function () { + $$PREBID_GLOBAL$$.bidderSettings = {}; + }) + + describe('buildRequests', function () { + const bidderRequest = { + auctionId: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + ortb2: { + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + } + } + }; + + it('should build a banner request', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: BANNER_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: BANNER_BID.bidId, + adUnitCode: BANNER_BID.adUnitCode, + mediaTypes: BANNER_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [BANNER_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should build a video request', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: VIDEO_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: VIDEO_BID.bidId, + adUnitCode: VIDEO_BID.adUnitCode, + mediaTypes: VIDEO_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [VIDEO_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should build a request with multiple mediatypes', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: MULTI_MEDIATYPES_BID.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: MULTI_MEDIATYPES_BID.bidId, + adUnitCode: MULTI_MEDIATYPES_BID.adUnitCode, + mediaTypes: MULTI_MEDIATYPES_BID.mediaTypes + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const bids = [MULTI_MEDIATYPES_BID] + const [syncRequest, auctionRequest] = spec.buildRequests(bids, {...bidderRequest, bids}); + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + + it('should not build a request if optimizations are there for the adunit code', function () { + const bids = [BANNER_BID] + const optimizations = { + [bids[0].adUnitCode]: {isEnabled: false, expiresAt: Date.now() + 600000} + } + + localStorage.setItem(OPTIMIZATIONS_STORAGE_KEY, JSON.stringify(optimizations)) + + const requests = spec.buildRequests(bids, {...bidderRequest, bids}); + + localStorage.removeItem(OPTIMIZATIONS_STORAGE_KEY) + + expect(requests).to.be.undefined + }) + + it('should exclude video requests where context is missing or not equal to outstream', function () { + const builtSyncRequest = { + gdpr: false, + ad_unit_codes: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.adUnitCode + } + + const builtBidRequest = { + id: '98bb5f61-4140-4ced-8b0e-65a33d792ab8', + impressions: [{ + id: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.bidId, + adUnitCode: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.adUnitCode, + mediaTypes: {banner: MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT.mediaTypes.banner} + }], + device: { + w: 1200, + h: 400, + dnt: 0 + }, + site: { + page: 'http://localhost.com', + domain: 'localhost.com' + }, + user: { + ids: {} + }, + options: { + useProfileApi: false + }, + regulations: { + gdpr: { + applies: false, + consentString: undefined + } + }, + timeout: 3600 + } + + const invalidVideoBids = [VIDEO_BID_WITH_MISSING_CONTEXT] + const multiMediatypesBidWithInvalidVideo = [MULTI_MEDIATYPES_WITH_INVALID_VIDEO_CONTEXT] + + expect(spec.buildRequests(invalidVideoBids, {...bidderRequest, bids: invalidVideoBids})).to.be.undefined + + const [syncRequest, auctionRequest] = spec.buildRequests(multiMediatypesBidWithInvalidVideo, {...bidderRequest, bids: multiMediatypesBidWithInvalidVideo}) + + expect(syncRequest.data).to.deep.equal(builtSyncRequest) + expect(auctionRequest.data).to.deep.equal(builtBidRequest) + }) + }) + + describe('interpretResponse', function () { + it('should return an empty array if the response is invalid', function () { + expect(spec.interpretResponse({body: 'INVALID_BODY'}, {})).to.deep.equal([]); + }) + + it('should return a formatted bid', function () { + const serverResponse = { + body: { + id: 'requestId', + bids: [{ + impressionId: '2f0d9715f60be8', + price: { + cpm: 1, + currency: 'JPY' + }, + dealId: 'TEST_DEAL_ID', + ad: { + creative: { + id: 'CREATIVE_ID', + mediaType: 'banner', + size: {width: 320, height: 250}, + markup: '
1
' + } + }, + ttl: 3600 + }], + optimizations: [ + { + adUnitCode: 'test_ad_unit_code', + isEnabled: false, + ttl: 12000 + }, + { + adUnitCode: 'test_ad_unit_code_2', + isEnabled: true, + ttl: 12000 + } + ] + } + } + + const formattedBid = { + requestId: '2f0d9715f60be8', + cpm: 1, + currency: 'JPY', + dealId: 'TEST_DEAL_ID', + ttl: 3600, + netRevenue: true, + creativeId: 'CREATIVE_ID', + mediaType: 'banner', + width: 320, + height: 250, + ad: '
1
', + adUrl: null + } + + const formattedResponse = spec.interpretResponse(serverResponse, {}) + const storedOptimizations = getOptimizationsFromLocalStorage() + + localStorage.removeItem(OPTIMIZATIONS_STORAGE_KEY) + + expect(formattedResponse).to.deep.equal([formattedBid]); + + expect(storedOptimizations['test_ad_unit_code']).to.exist + expect(storedOptimizations['test_ad_unit_code'].isEnabled).to.equal(false) + expect(storedOptimizations['test_ad_unit_code'].expiresAt).to.be.a('number') + + expect(storedOptimizations['test_ad_unit_code_2']).to.exist + expect(storedOptimizations['test_ad_unit_code_2'].isEnabled).to.equal(true) + expect(storedOptimizations['test_ad_unit_code_2'].expiresAt).to.be.a('number') + }) + }); + + describe('getUserSyncs', function () { + it('should return an empty response if the response is invalid or missing data', function () { + expect(spec.getUserSyncs(null, [{body: 'INVALID_BODY'}])).to.be.undefined; + expect(spec.getUserSyncs(null, [{body: 'INVALID_BODY'}, {body: 'INVALID_BODY'}])).to.be.undefined; + }) + + it('should return an array of user syncs', function () { + const serverResponses = [ + { + body: { + bidders: [ + {type: 'image', url: 'https://test-url.com'}, + {type: 'redirect', url: 'https://test-url.com'}, + {type: 'iframe', url: 'https://test-url.com'} + ] + } + }, + { + body: 'BID-RESPONSE-DATA' + } + ] + + const formattedUserSyncs = [ + {type: 'image', url: 'https://test-url.com'}, + {type: 'image', url: 'https://test-url.com'}, + {type: 'iframe', url: 'https://test-url.com'} + ] + + expect(spec.getUserSyncs(null, serverResponses)).to.deep.equal(formattedUserSyncs); + }) + }); +}); diff --git a/test/spec/modules/mediafuseBidAdapter_spec.js b/test/spec/modules/mediafuseBidAdapter_spec.js index dd2b5df70bd..1fb09265d56 100644 --- a/test/spec/modules/mediafuseBidAdapter_spec.js +++ b/test/spec/modules/mediafuseBidAdapter_spec.js @@ -35,23 +35,23 @@ describe('MediaFuseAdapter', function () { }); it('should return true when required params found', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'member': '1234', 'invCode': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(true); + expect(spec.isBidRequestValid(invalidBid)).to.equal(true); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/mediagoBidAdapter_spec.js b/test/spec/modules/mediagoBidAdapter_spec.js index 6e58217b3d3..5a1545ae028 100644 --- a/test/spec/modules/mediagoBidAdapter_spec.js +++ b/test/spec/modules/mediagoBidAdapter_spec.js @@ -3,14 +3,11 @@ import { spec, getPmgUID, storage, - getPageTitle, - getPageDescription, - getPageKeywords, - getConnectionDownLink, THIRD_PARTY_COOKIE_ORIGIN, COOKIE_KEY_MGUID, getCurrentTimeToUTCString } from 'modules/mediagoBidAdapter.js'; +import { getPageTitle, getPageDescription, getPageKeywords, getConnectionDownLink } from '../../../libraries/fpdUtils/pageInfo.js'; import * as utils from 'src/utils.js'; describe('mediago:BidAdapterTests', function () { @@ -260,43 +257,43 @@ describe('mediago:BidAdapterTests', function () { expect(bid.height).to.equal(250); expect(bid.currency).to.equal('USD'); }); +}); - describe('mediago: getUserSyncs', function() { - const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; - const IFRAME_ENABLED = { - iframeEnabled: true, - pixelEnabled: false, - }; - const IFRAME_DISABLED = { - iframeEnabled: false, - pixelEnabled: false, - }; - const GDPR_CONSENT = { - consentString: 'gdprConsentString', - gdprApplies: true - }; - const USP_CONSENT = { - consentString: 'uspConsentString' +describe('mediago: getUserSyncs', function() { + const COOKY_SYNC_IFRAME_URL = 'https://cdn.mediago.io/js/cookieSync.html'; + const IFRAME_ENABLED = { + iframeEnabled: true, + pixelEnabled: false, + }; + const IFRAME_DISABLED = { + iframeEnabled: false, + pixelEnabled: false, + }; + const GDPR_CONSENT = { + consentString: 'gdprConsentString', + gdprApplies: true + }; + const USP_CONSENT = { + consentString: 'uspConsentString' + } + + let syncParamUrl = `dm=${encodeURIComponent(location.origin || `https://${location.host}`)}`; + syncParamUrl += '&gdpr=1&gdpr_consent=gdprConsentString&ccpa_consent=uspConsentString'; + const expectedIframeSyncs = [ + { + type: 'iframe', + url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` } + ]; - let syncParamUrl = `dm=${encodeURIComponent(location.origin || `https://${location.host}`)}`; - syncParamUrl += '&gdpr=1&gdpr_consent=gdprConsentString&ccpa_consent=uspConsentString'; - const expectedIframeSyncs = [ - { - type: 'iframe', - url: `${COOKY_SYNC_IFRAME_URL}?${syncParamUrl}` - } - ]; - - it('should return nothing if iframe is disabled', () => { - const userSyncs = spec.getUserSyncs(IFRAME_DISABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); - expect(userSyncs).to.be.undefined; - }); + it('should return nothing if iframe is disabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_DISABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.be.undefined; + }); - it('should do userSyncs if iframe is enabled', () => { - const userSyncs = spec.getUserSyncs(IFRAME_ENABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); - expect(userSyncs).to.deep.equal(expectedIframeSyncs); - }); + it('should do userSyncs if iframe is enabled', () => { + const userSyncs = spec.getUserSyncs(IFRAME_ENABLED, undefined, GDPR_CONSENT, USP_CONSENT, undefined); + expect(userSyncs).to.deep.equal(expectedIframeSyncs); }); }); diff --git a/test/spec/modules/mediaimpactBidAdapter_spec.js b/test/spec/modules/mediaimpactBidAdapter_spec.js index 3d706e59c3f..5bf088c0334 100644 --- a/test/spec/modules/mediaimpactBidAdapter_spec.js +++ b/test/spec/modules/mediaimpactBidAdapter_spec.js @@ -1,6 +1,7 @@ import {expect} from 'chai'; import {spec, ENDPOINT_PROTOCOL, ENDPOINT_DOMAIN, ENDPOINT_PATH} from 'modules/mediaimpactBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import * as miUtils from 'libraries/mediaImpactUtils/index.js'; const BIDDER_CODE = 'mediaimpact'; @@ -117,7 +118,7 @@ describe('MediaimpactAdapter', function () { describe('joinSizesToString', function () { it('success convert sizes list to string', function () { - const sizesStr = spec.joinSizesToString([[300, 250], [300, 600]]); + const sizesStr = miUtils.joinSizesToString([[300, 250], [300, 600]]); expect(sizesStr).to.equal('300x250|300x600'); }); }); @@ -139,9 +140,9 @@ describe('MediaimpactAdapter', function () { 'width': 300, 'height': 250, 'creativeId': '8:123456', - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, 'syncs': [ {'type': 'image', 'url': 'https://test.domain/tracker_1.gif'}, {'type': 'image', 'url': 'https://test.domain/tracker_2.gif'}, @@ -200,9 +201,9 @@ describe('MediaimpactAdapter', function () { 'cpm': 0.01, 'currency': 'USD', 'netRevenue': true, - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, }; it('fill ad for response', function () { @@ -238,7 +239,7 @@ describe('MediaimpactAdapter', function () { let ajaxStub; beforeEach(() => { - ajaxStub = sinon.stub(spec, 'postRequest') + ajaxStub = sinon.stub(miUtils, 'postRequest') }) afterEach(() => { @@ -263,9 +264,9 @@ describe('MediaimpactAdapter', function () { 'width': 300, 'height': 250, 'creativeId': '8:123456', - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, 'syncs': [ {'type': 'image', 'link': 'https://test.domain/tracker_1.gif'}, {'type': 'image', 'link': 'https://test.domain/tracker_2.gif'}, diff --git a/test/spec/modules/medianetAnalyticsAdapter_spec.js b/test/spec/modules/medianetAnalyticsAdapter_spec.js index 7c3cf88dace..8a298acae80 100644 --- a/test/spec/modules/medianetAnalyticsAdapter_spec.js +++ b/test/spec/modules/medianetAnalyticsAdapter_spec.js @@ -210,7 +210,7 @@ describe('Media.net Analytics Adapter', function() { expect(noBidLog.pvnm).to.have.ordered.members(['-2', 'bidder1', 'bidder1', 'medianet']); expect(noBidLog.mtype).to.have.ordered.members([banner, banner, banner, banner]); expect(noBidLog.status).to.have.ordered.members(['1', '2', '2', '2']); - expect(noBidLog.size).to.have.ordered.members(['', '', '', '']); + expect(noBidLog.size).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']); expect(noBidLog.szs).to.have.ordered.members(['300x100|300x250', '300x100', '300x250', '300x250|300x100']); }); @@ -225,7 +225,7 @@ describe('Media.net Analytics Adapter', function() { it('should have winner log in standard auction', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); - let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); medianetAnalytics.clearlogsQueue(); expect(winnerLog.length).to.equal(1); @@ -234,7 +234,7 @@ describe('Media.net Analytics Adapter', function() { it('should have correct values in winner log', function() { medianetAnalytics.clearlogsQueue(); performStandardAuctionWithWinner(); - let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); medianetAnalytics.clearlogsQueue(); expect(winnerLog[0]).to.include({ @@ -258,7 +258,7 @@ describe('Media.net Analytics Adapter', function() { 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); + let winnerLog = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter((log) => log.winner === '1'); medianetAnalytics.clearlogsQueue(); expect(winnerLog[0]).to.include({ @@ -309,32 +309,32 @@ describe('Media.net Analytics Adapter', function() { it('should pick winning bid if multibids with same request id', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; expect(winningBid.adid).equals('3e6e4bce5c8fb3'); medianetAnalytics.clearlogsQueue(); const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_DIFF_CPM).reverse(); performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); - winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); it('should pick winning bid if multibids with same request id and same time to respond', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_DIFF_CPM_SAME_TIME); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; expect(winningBid.adid).equals('3e6e4bce5c8fb3'); medianetAnalytics.clearlogsQueue(); }); it('should pick winning bid if multibids with same request id and equal cpm', function() { performStandardAuctionMultiBidWithSameRequestId(MOCK.BIDS_SAME_REQ_EQUAL_CPM); - let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + let winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; expect(winningBid.adid).equals('3e6e4bce5c8fb3'); medianetAnalytics.clearlogsQueue(); const reversedResponseArray = [].concat(MOCK.BIDS_SAME_REQ_EQUAL_CPM).reverse(); performStandardAuctionMultiBidWithSameRequestId(reversedResponseArray); - winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner)[0]; + winningBid = medianetAnalytics.getlogsQueue().map((log) => getQueryData(log)).filter(log => log.winner === '1')[0]; expect(winningBid.adid).equals('3e6e4bce5c8fb3'); }); diff --git a/test/spec/modules/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 4a221e97444..5f2efc7864f 100644 --- a/test/spec/modules/medianetBidAdapter_spec.js +++ b/test/spec/modules/medianetBidAdapter_spec.js @@ -1,7 +1,8 @@ import {expect, assert} from 'chai'; -import {spec} from 'modules/medianetBidAdapter.js'; +import {spec, EVENT_PIXEL_URL, EVENTS} from 'modules/medianetBidAdapter.js'; import { makeSlot } from '../integration/faker/googletag.js'; import { config } from 'src/config.js'; +import {server} from '../../mocks/xhr.js'; $$PREBID_GLOBAL$$.version = $$PREBID_GLOBAL$$.version || 'version'; let VALID_BID_REQUEST = [{ @@ -174,6 +175,37 @@ let VALID_BID_REQUEST = [{ }, 'bidRequestsCount': 1 }], + // Protected Audience API Request + VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'bidRequestsCount': 1 + }], + VALID_BID_REQUEST_WITH_USERID = [{ 'bidder': 'medianet', 'params': { @@ -233,6 +265,72 @@ let VALID_BID_REQUEST = [{ 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', 'bidRequestsCount': 1 }], + VALID_BID_REQUEST_WITH_USERIDASEIDS = [{ + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + userIdAsEids: [{ + 'source': 'criteo.com', + 'uids': [ + { + 'id': 'VZME3l9ycFFORncwaGJRVUNtUzB1UVhpWVd5TElrR3A5SHVSWXAwSFVPJTJCWiUyRnV2UTBPWjZOZ1ZrWnN4SldxcWUlMkJhUnFmUVNzUVg4N1NsdW84SGpUU1BsUllQSnN5bERMdFdPM2pWVXAlMkZVSSUyQkZsJTJGcktlenpZaHp0YXlvU25INWRQQ2tXciUyRk9PQmdac3RHeG9adDNKVzlRWE51ZyUzRCUzRA', + 'atype': 1 + } + ] + } + ], + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + ortb2Imp: { + ext: { + tid: '277b631f-92f5-4844-8b19-ea13c095d3f1', + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 250]], + } + }, + 'bidId': '28f8f8130a583e', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }, { + 'bidder': 'medianet', + 'params': { + 'crid': 'crid', + 'cid': 'customer_id', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-123', + ortb2Imp: { + ext: { + tid: 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[300, 251]], + } + }, + 'sizes': [[300, 251]], + 'bidId': '3f97ca71b1e5c2', + 'bidderRequestId': '1e9b1f07797c1c', + 'auctionId': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidRequestsCount': 1 + }], VALID_BID_REQUEST_INVALID_BIDFLOOR = [{ 'bidder': 'medianet', @@ -426,13 +524,23 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'usp_applies': false, 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -504,6 +612,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_NATIVE = { @@ -516,13 +625,23 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'usp_applies': false, 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -595,6 +714,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD = { @@ -607,13 +727,23 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'usp_applies': false, 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -684,6 +814,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_WITH_USERID = { @@ -696,7 +827,7 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'user_id': { britepoolid: '82efd5e1-816b-4f87-97f8-044f407e2911' @@ -706,6 +837,120 @@ let VALID_BID_REQUEST = [{ 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } + } + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [{ + 'id': '28f8f8130a583e', + ortb2Imp: VALID_BID_REQUEST_WITH_USERID[0].ortb2Imp, + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'tagid': 'crid', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 250 + }], + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }, { + 'id': '3f97ca71b1e5c2', + ortb2Imp: VALID_BID_REQUEST_WITH_USERID[1].ortb2Imp, + 'transactionId': 'c52a5c62-3c2b-4b90-9ff8-ec1487754822', + 'tagid': 'crid', + 'ext': { + 'dfp_id': 'div-gpt-ad-1460505748561-123', + 'visibility': 1, + 'viewability': 1, + 'coordinates': { + 'top_left': { + x: 50, + y: 50 + }, + 'bottom_right': { + x: 100, + y: 100 + } + }, + 'display_count': 1 + }, + 'banner': [{ + 'w': 300, + 'h': 251 + }], + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'isTop': true + } + } + }], + 'ortb2': {}, + 'tmax': config.getConfig('bidderTimeout') + }, + VALID_PAYLOAD_WITH_USERIDASEIDS = { + 'site': { + 'page': 'http://media.net/prebidtest', + 'domain': 'media.net', + 'ref': 'http://media.net/prebidtest', + 'topMostLocation': 'http://media.net/topmost', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': 'v' + '$prebid.version$', + 'gdpr_applies': false, + 'usp_applies': false, + 'coppa_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -780,6 +1025,20 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': { + 'user': { + 'ext': { + 'eids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'VZME3l9ycFFORncwaGJRVUNtUzB1UVhpWVd5TElrR3A5SHVSWXAwSFVPJTJCWiUyRnV2UTBPWjZOZ1ZrWnN4SldxcWUlMkJhUnFmUVNzUVg4N1NsdW84SGpUU1BsUllQSnN5bERMdFdPM2pWVXAlMkZVSSUyQkZsJTJGcktlenpZaHp0YXlvU25INWRQQ2tXciUyRk9PQmdac3RHeG9adDNKVzlRWE51ZyUzRCUzRA', + 'atype': 1 + } + ] + }] + } + }, + }, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_WITH_CRID = { @@ -792,13 +1051,23 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'usp_applies': false, 'coppa_applies': true, 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -873,8 +1142,89 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, + // Protected Audience API Valid Payload + VALID_PAYLOAD_PAAPI = { + 'site': { + 'domain': 'media.net', + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest', + 'topMostLocation': 'http://media.net/topmost', + 'isTop': true + }, + 'ext': { + 'customer_id': 'customer_id', + 'prebid_version': 'v' + '$prebid.version$', + 'gdpr_applies': false, + 'usp_applies': false, + 'coppa_applies': false, + 'screen': { + 'w': 1000, + 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } + } + }, + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'imp': [ + { + 'id': '28f8f8130a583e', + 'transactionId': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ext': { + 'ae': 1, + 'dfp_id': 'div-gpt-ad-1460505748561-0', + 'display_count': 1, + 'coordinates': { + 'top_left': { + 'x': 50, + 'y': 50 + }, + 'bottom_right': { + 'x': 100, + 'y': 100 + } + }, + 'viewability': 1, + 'visibility': 1 + }, + 'all': { + 'cid': 'customer_id', + 'crid': 'crid', + 'site': { + 'domain': 'media.net', + 'isTop': true, + 'page': 'http://media.net/prebidtest', + 'ref': 'http://media.net/prebidtest' + } + }, + 'ortb2Imp': { + 'ext': { + 'tid': '277b631f-92f5-4844-8b19-ea13c095d3f1', + 'ae': 1 + } + }, + 'banner': [ + { + 'w': 300, + 'h': 250 + } + ], + 'tagid': 'crid' + } + ], + 'ortb2': {}, + 'tmax': 3000 + }, VALID_VIDEO_BID_REQUEST = [{ 'bidder': 'medianet', @@ -1104,6 +1454,126 @@ let VALID_BID_REQUEST = [{ } } }, + // Protected Audience API Response + SERVER_RESPONSE_PAAPI = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'paApiAuctionConfigs': [ + { + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + // Protected Audience API OpenRTB Response + SERVER_RESPONSE_PAAPI_ORTB = { + body: { + 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'bidList': [{ + 'no_bid': false, + 'requestId': '28f8f8130a583e', + 'ad': 'ad', + 'width': 300, + 'height': 250, + 'creativeId': 'crid', + 'netRevenue': true, + 'cpm': 0.1 + }], + 'ext': { + 'igi': [{ + 'igs': [ + { + 'impid': '28f8f8130a583e', + 'bidId': '28f8f8130a583e', + 'config': { + 'seller': 'https://hbx.test.media.net', + 'decisionLogicUrl': 'https://hbx.test.media.net/decision-logic.js', + 'interestGroupBuyers': ['https://buyer.test.media.net'], + 'auctionSignals': { + 'logging_params': { + 'cid': 'customer_id', + 'crid': 'crid', + 'bid_uuid': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', + 'browser_id': 2, + 'dfpid': 'div-gpt-ad-1460505748561-0' + }, + 'pvidLookup': { + 'https://buyer.test.media.net': { + 'pvid': '172', + 'seat': 'quantcast-qc1' + } + }, + 'bidFlr': 0.0 + }, + 'sellerTimout': 1000, + 'sellerSignals': { + 'callbackURL': 'https://test.com/paapi/v1/abcd' + }, + 'perBuyerSignals': { + 'https://buyer.test.media.net': [ 'test_buyer_signals' ] + }, + 'perBuyerTimeouts': { + '*': 200 + } + } + } + ], + }], + 'csUrl': [{ + 'type': 'iframe', + 'url': 'http://contextual.media.net/checksync.php?&vsSync=1' + }] + } + } + }, + SERVER_VIDEO_OUTSTREAM_RESPONSE_VALID_BID = { body: { 'id': 'd90ca32f-3877-424a-b2f2-6a68988df57a', @@ -1231,7 +1701,7 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_consent_string': 'consentString', 'gdpr_applies': true, 'usp_applies': true, @@ -1240,6 +1710,16 @@ let VALID_BID_REQUEST = [{ 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -1310,6 +1790,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': 3000, }, VALID_BIDDER_REQUEST_WITH_GPP_IN_ORTB2 = { @@ -1339,13 +1820,23 @@ let VALID_BID_REQUEST = [{ }, 'ext': { 'customer_id': 'customer_id', - 'prebid_version': $$PREBID_GLOBAL$$.version, + 'prebid_version': 'v' + '$prebid.version$', 'gdpr_applies': false, 'usp_applies': false, 'coppa_applies': false, 'screen': { 'w': 1000, 'h': 1000 + }, + 'vcoords': { + 'top_left': { + 'x': 50, + 'y': 100 + }, + 'bottom_right': { + 'x': 490, + 'y': 880 + } } }, 'id': 'aafabfd0-28c0-4ac0-aa09-99689e88b81d', @@ -1428,6 +1919,10 @@ describe('Media.net bid adapter', function () { let sandbox; beforeEach(function () { sandbox = sinon.sandbox.create(); + sandbox.stub(window.top, 'innerHeight').value(780) + sandbox.stub(window.top, 'innerWidth').value(440) + sandbox.stub(window.top, 'scrollY').value(100) + sandbox.stub(window.top, 'scrollX').value(50) }); afterEach(function () { @@ -1547,9 +2042,27 @@ describe('Media.net bid adapter', function () { expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERID); }); + it('should have userIdAsEids in bid request', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_USERIDASEIDS, VALID_AUCTIONDATA); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_WITH_USERIDASEIDS); + }); + + it('should have valid payload when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); + expect(JSON.parse(bidReq.data)).to.deep.equal(VALID_PAYLOAD_PAAPI); + }); + + it('should send whatever is set in ortb2imp.ext.ae in all bid requests when PAAPI is enabled', function () { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); + let data = JSON.parse(bidReq.data); + expect(data).to.deep.equal(VALID_PAYLOAD_PAAPI); + expect(data.imp[0].ext).to.have.property('ae'); + expect(data.imp[0].ext.ae).to.equal(1); + }); + describe('build requests: when page meta-data is available', () => { beforeEach(() => { - spec.clearMnData(); + spec.clearPageMeta(); }); it('should pass canonical, twitter and fb paramters if available', () => { let documentStub = sandbox.stub(window.top.document, 'querySelector'); @@ -1721,19 +2234,140 @@ describe('Media.net bid adapter', function () { let bids = spec.interpretResponse(SERVER_RESPONSE_EMPTY_BIDLIST, []); expect(bids).to.deep.equal(validBids); }); + + it('should return paapi if PAAPI response is received', function() { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI.body.ext.paApiAuctionConfigs[0]); + }); + + it('should return paapi if openRTB PAAPI response received', function () { + let response = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(response).to.have.property('bids'); + expect(response).to.have.property('paapi'); + expect(response.paapi[0]).to.deep.equal(SERVER_RESPONSE_PAAPI_ORTB.body.ext.igi[0].igs[0]) + }); + + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI, []); + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); + + it('should have the correlation between paapi[0].bidId and bidreq.imp[0].id for openRTB response', function() { + let bidReq = spec.buildRequests(VALID_BID_REQUEST_WITH_AE_IN_ORTB2IMP, {...VALID_AUCTIONDATA, paapi: {enabled: true}}); + let bidRes = spec.interpretResponse(SERVER_RESPONSE_PAAPI_ORTB, []); + expect(bidRes.paapi[0].bidId).to.equal(JSON.parse(bidReq.data).imp[0].id) + }); }); describe('onTimeout', function () { - it('should have valid timeout data', function() { - let response = spec.onTimeout({}); - expect(response).to.deep.equal(undefined); + it('onTimeout exist as a function', () => { + assert.typeOf(spec.onTimeout, 'function'); + }); + it('should send timeout data correctly', function () { + const timeoutData = [{ + bidder: 'medianet', + bidId: 'mnet-4644-442a-b5e0-93f268cf8d19', + adUnitCode: 'adUnit-code', + timeout: 3000, + auctionId: '12a34b56c' + }]; + sandbox.stub(window.navigator, 'sendBeacon').returns(false); + + spec.onTimeout(timeoutData); + const reqBody = new URLSearchParams(server.requests[0].requestBody); + + assert.equal(server.requests[0].method, 'POST'); + assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(reqBody.get('event'), EVENTS.TIMEOUT_EVENT_NAME); + assert.equal(reqBody.get('rd'), timeoutData[0].timeout.toString()); + assert.equal(reqBody.get('acid[]'), timeoutData[0].auctionId); }); }); describe('onBidWon', function () { - it('should have valid bid data', function() { - let response = spec.onBidWon(undefined); - expect(response).to.deep.equal(undefined); + it('onBidWon exist as a function', () => { + assert.typeOf(spec.onBidWon, 'function'); + }); + it('should send winning bid data correctly', function () { + const bid = { + bidder: 'medianet', + bidId: 'mnet-4644-442a-b5e0-93f268cf8d19', + adUnitCode: 'adUnit-code', + timeout: 3000, + auctionId: '12a34b56c', + cpm: 12.24 + }; + sandbox.stub(window.navigator, 'sendBeacon').returns(false); + + spec.onBidWon(bid); + const reqBody = new URLSearchParams(server.requests[0].requestBody); + + assert.equal(server.requests[0].method, 'POST'); + assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(reqBody.get('event'), EVENTS.BID_WON_EVENT_NAME); + assert.equal(reqBody.get('value'), bid.cpm.toString()); + assert.equal(reqBody.get('acid[]'), bid.auctionId); + }); + }); + + describe('onSetTargeting', function () { + it('onSetTargeting exist as a function', () => { + assert.typeOf(spec.onSetTargeting, 'function'); + }); + it('should send targeting data correctly', function () { + const bid = { + bidder: 'medianet', + bidId: 'mnet-4644-442a-b5e0-93f268cf8d19', + adUnitCode: 'adUnit-code', + timeout: 3000, + auctionId: '12a34b56c', + cpm: 12.24 + }; + sandbox.stub(window.navigator, 'sendBeacon').returns(false); + sandbox.stub(config, 'getConfig').withArgs('enableSendAllBids').returns(false); + + spec.onSetTargeting(bid); + const reqBody = new URLSearchParams(server.requests[0].requestBody); + + assert.equal(server.requests[0].method, 'POST'); + assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(reqBody.get('event'), EVENTS.SET_TARGETING); + assert.equal(reqBody.get('value'), bid.cpm.toString()); + assert.equal(reqBody.get('acid[]'), bid.auctionId); + }); + }); + + describe('onBidderError', function () { + it('onBidderError exist as a function', () => { + assert.typeOf(spec.onBidderError, 'function'); + }); + it('should send bidderError data correctly', function () { + const error = { + reason: {message: 'Failed to fetch', status: 500}, + timedOut: true, + status: 0 + } + const bids = [{ + bidder: 'medianet', + bidId: 'mnet-4644-442a-b5e0-93f268cf8d19', + adUnitCode: 'adUnit-code', + timeout: 3000, + auctionId: '12a34b56c', + cpm: 12.24 + }]; + sandbox.stub(window.navigator, 'sendBeacon').returns(false); + + spec.onBidderError({error, bidderRequest: {bids}}); + const reqBody = new URLSearchParams(server.requests[0].requestBody); + + assert.equal(server.requests[0].method, 'POST'); + assert.equal(server.requests[0].url, EVENT_PIXEL_URL); + assert.equal(reqBody.get('event'), EVENTS.BIDDER_ERROR); + assert.equal(reqBody.get('rd'), `timedOut:${error.timedOut}|status:${error.status}|message:${error.reason.message}`); + assert.equal(reqBody.get('acid[]'), bids[0].auctionId); }); }); diff --git a/test/spec/modules/merkleIdSystem_spec.js b/test/spec/modules/merkleIdSystem_spec.js index 82c17336d20..b12bb365e5b 100644 --- a/test/spec/modules/merkleIdSystem_spec.js +++ b/test/spec/modules/merkleIdSystem_spec.js @@ -3,6 +3,8 @@ import * as utils from 'src/utils.js'; import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; import sinon from 'sinon'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; let expect = require('chai').expect; @@ -248,4 +250,71 @@ describe('Merkle System', function () { expect(callbackSpy.calledOnce).to.be.true; }); }); + + describe('eid', () => { + before(() => { + attachIdSystem(merkleIdSubmodule); + }); + it('merkleId (legacy) - supports single id', function() { + const userId = { + 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: 'merkleinc.com', + uids: [{ + id: 'some-random-id-value', + atype: 3, + ext: { keyID: 1 } + }] + }); + }); + + it('merkleId supports multiple source providers', function() { + const userId = { + merkleId: [{ + id: 'some-random-id-value', ext: { enc: 1, keyID: 16, idName: 'pamId', ssp: 'ssp1' } + }, { + id: 'another-random-id-value', + ext: { + enc: 1, + idName: 'pamId', + third: 4, + ssp: 'ssp2' + } + }] + } + + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(2); + expect(newEids[0]).to.deep.equal({ + source: 'ssp1.merkleinc.com', + uids: [{id: 'some-random-id-value', + atype: 3, + ext: { + enc: 1, + keyID: 16, + idName: 'pamId', + ssp: 'ssp1' + } + }] + }); + expect(newEids[1]).to.deep.equal({ + source: 'ssp2.merkleinc.com', + uids: [{id: 'another-random-id-value', + atype: 3, + ext: { + third: 4, + enc: 1, + idName: 'pamId', + ssp: 'ssp2' + } + }] + }); + }); + }) }); diff --git a/test/spec/modules/mgidBidAdapter_spec.js b/test/spec/modules/mgidBidAdapter_spec.js index f9bb1fb91e1..180b0dc723e 100644 --- a/test/spec/modules/mgidBidAdapter_spec.js +++ b/test/spec/modules/mgidBidAdapter_spec.js @@ -320,6 +320,14 @@ describe('Mgid bid adapter', function () { let abid = { adUnitCode: 'div', bidder: 'mgid', + ortb2Imp: { + ext: { + gpid: '/1111/gpid', + data: { + pbadslot: '/1111/gpid', + } + } + }, params: { accountId: '1', placementId: '2', @@ -447,12 +455,13 @@ 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].ext.gpid).to.deep.equal('/1111/gpid'); expect(data.imp[0].banner).to.deep.equal({w: 300, h: 250}); 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":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":250}}],"tmax":3000}`, + '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},"ext":{"gpid":"/1111/gpid"},"banner":{"w":300,"h":250}}],"tmax":3000}`, }); }); it('should not return native imp if minimum asset list not requested', function () { @@ -496,12 +505,13 @@ 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].ext.gpid).to.deep.equal('/1111/gpid'); expect(data.imp[0].native).is.a('object').and.to.deep.equal({'request': {'assets': [{'id': 1, 'required': 1, 'title': {'len': 80}}, {'id': 2, 'img': {'h': 80, 'type': 3, 'w': 80}, 'required': 0}, {'data': {'type': 1}, 'id': 11, 'required': 0}], 'plcmtcnt': 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":"${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}}]}}}],"tmax":3000}`, + '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},"ext":{"gpid":"/1111/gpid"},"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}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with image altered', function () { @@ -538,7 +548,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":"${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}}]}}}],"tmax":3000}`, + '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},"ext":{"gpid":"/1111/gpid"},"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}}]}}}],"tmax":3000}`, }); }); it('should return proper native imp with sponsoredBy', function () { @@ -574,7 +584,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":"${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}}]}}}],"tmax":3000}`, + '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},"ext":{"gpid":"/1111/gpid"},"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}}]}}}],"tmax":3000}`, }); }); it('should return proper banner request', function () { @@ -608,7 +618,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":"${version}"},"imp":[{"tagid":"2/div","secure":${secure},"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}],"tmax":3000}`, + '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},"ext":{"gpid":"/1111/gpid"},"banner":{"w":300,"h":600,"format":[{"w":300,"h":600},{"w":300,"h":250}],"pos":1}}],"tmax":3000}`, }); }); it('should proper handle ortb2 data', function () { diff --git a/test/spec/modules/mgidXBidAdapter_spec.js b/test/spec/modules/mgidXBidAdapter_spec.js index 9efaf94c954..9b39b307dc4 100644 --- a/test/spec/modules/mgidXBidAdapter_spec.js +++ b/test/spec/modules/mgidXBidAdapter_spec.js @@ -5,9 +5,19 @@ import { getUniqueIdentifierStr } from '../../../src/utils.js'; import { config } from '../../../src/config'; import { USERSYNC_DEFAULT_CONFIG } from '../../../src/userSync'; -const bidder = 'mgidX' +const bidder = 'mgidX'; describe('MGIDXBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -19,8 +29,9 @@ describe('MGIDXBidAdapter', function () { }, params: { region: 'eu', - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -33,8 +44,9 @@ describe('MGIDXBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -57,8 +69,9 @@ describe('MGIDXBidAdapter', function () { }, params: { region: 'eu', - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -82,9 +95,17 @@ describe('MGIDXBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - timeout: 1000 + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -113,7 +134,7 @@ describe('MGIDXBidAdapter', function () { it('Returns valid EU URL', function () { bids[0].params.region = 'eu'; serverRequest = spec.buildRequests(bids, bidderRequest); - expect(serverRequest.url).to.equal('https://eu.mgid.com/pbjs'); + expect(serverRequest.url).to.equal('https://eu-x.mgid.com/pbjs'); }); it('Returns valid EAST URL', function () { @@ -127,6 +148,7 @@ describe('MGIDXBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -160,6 +182,56 @@ describe('MGIDXBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -205,6 +277,38 @@ describe('MGIDXBidAdapter', function () { }); }); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + describe('interpretResponse', function () { it('Should interpret banner response', function () { const banner = { diff --git a/test/spec/modules/microadBidAdapter_spec.js b/test/spec/modules/microadBidAdapter_spec.js index 9eb36d2fa6c..ac1738685db 100644 --- a/test/spec/modules/microadBidAdapter_spec.js +++ b/test/spec/modules/microadBidAdapter_spec.js @@ -301,10 +301,6 @@ describe('microadBidAdapter', () => { userId: {novatiq: {snowflake: 'novatiq-sample'}}, expected: {aids: JSON.stringify([{type: 10, id: 'novatiq-sample'}])} }, - 'Parrable ID': { - userId: {parrableId: {eid: 'parrable-sample'}}, - expected: {aids: JSON.stringify([{type: 11, id: 'parrable-sample'}])} - }, 'AudienceOne User ID': { userId: {dacId: {id: 'audience-one-sample'}}, expected: {aids: JSON.stringify([{type: 12, id: 'audience-one-sample'}])} diff --git a/test/spec/modules/minutemediaBidAdapter_spec.js b/test/spec/modules/minutemediaBidAdapter_spec.js index cf50ad2cd0a..f2bdd3b6c9d 100644 --- a/test/spec/modules/minutemediaBidAdapter_spec.js +++ b/test/spec/modules/minutemediaBidAdapter_spec.js @@ -440,6 +440,8 @@ describe('minutemediaAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: VIDEO }, @@ -449,6 +451,8 @@ describe('minutemediaAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER }] @@ -461,7 +465,7 @@ describe('minutemediaAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +480,10 @@ describe('minutemediaAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +496,8 @@ describe('minutemediaAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { diff --git a/test/spec/modules/missenaBidAdapter_spec.js b/test/spec/modules/missenaBidAdapter_spec.js index ab1fbdcc074..6a143fa49ef 100644 --- a/test/spec/modules/missenaBidAdapter_spec.js +++ b/test/spec/modules/missenaBidAdapter_spec.js @@ -1,6 +1,8 @@ import { expect } from 'chai'; import { spec, storage } from 'modules/missenaBidAdapter.js'; import { BANNER } from '../../../src/mediaTypes.js'; +import { config } from 'src/config.js'; +import * as autoplay from 'libraries/autoplayDetection/autoplay.js'; const REFERRER = 'https://referer'; const REFERRER2 = 'https://referer2'; @@ -12,6 +14,9 @@ describe('Missena Adapter', function () { storageAllowed: true, }, }; + let sandbox = sinon.sandbox.create(); + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + sandbox.stub(autoplay, 'isAutoplayEnabled').returns(false); const bidId = 'abc'; const bid = { @@ -29,6 +34,12 @@ describe('Missena Adapter', function () { placement: 'sticky', formats: ['sticky-banner'], }, + schain: { + validation: 'strict', + config: { + ver: '1.0', + }, + }, getFloor: (inputParams) => { if (inputParams.mediaType === BANNER) { return { @@ -58,10 +69,12 @@ describe('Missena Adapter', function () { consentString: consentString, gdprApplies: true, }, + uspConsent: 'IDO', refererInfo: { topmostLocation: REFERRER, canonicalUrl: 'https://canonical', }, + ortb2: { regs: { coppa: 1 } }, }; const bids = [bid, bidWithoutFloor]; @@ -100,6 +113,23 @@ describe('Missena Adapter', function () { const payload = JSON.parse(request.data); const payloadNoFloor = JSON.parse(requests[1].data); + it('should send disabled autoplay', function () { + expect(payload.autoplay).to.equal(0); + }); + + it('should contain coppa', function () { + expect(payload.coppa).to.equal(1); + }); + sandbox.restore(); + + it('should contain uspConsent', function () { + expect(payload.us_privacy).to.equal('IDO'); + }); + + it('should contain schain', function () { + expect(payload.schain.config.ver).to.equal('1.0'); + }); + it('should return as many server requests as bidder requests', function () { expect(requests.length).to.equal(2); }); @@ -113,11 +143,11 @@ describe('Missena Adapter', function () { }); it('should send placement', function () { - expect(payload.placement).to.equal('sticky'); + expect(payload.params.placement).to.equal('sticky'); }); it('should send formats', function () { - expect(payload.formats).to.eql(['sticky-banner']); + expect(payload.params.formats).to.eql(['sticky-banner']); }); it('should send referer information to the request', function () { diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index a4e58afbd1b..1bf1ec12bc4 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -1,148 +1,269 @@ -import {expect} from 'chai'; -import {spec} from '../../../modules/mobfoxpbBidAdapter.js'; +import { expect } from 'chai'; +import { spec } from '../../../modules/mobfoxpbBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'mobfoxpb'; describe('MobfoxHBBidAdapter', function () { - const bid = { - bidId: '23fhj33i987f', - bidder: 'mobfoxpb', + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, mediaTypes: { [BANNER]: { sizes: [[300, 250]] } }, params: { - placementId: 783, - traffic: BANNER + } - }; + } const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'test.com' + referer: 'https://test.com', + page: 'https://test.com' }, - ortb2: {} + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { it('Should return true if there are bidId, params and key parameters present', function () { - expect(spec.isBidRequestValid(bid)).to.be.true; + expect(spec.isBidRequestValid(bids[0])).to.be.true; }); it('Should return false if at least one of parameters is not present', function () { - delete bid.params.placementId; - expect(spec.isBidRequestValid(bid)).to.be.false; + expect(spec.isBidRequestValid(invalidBid)).to.be.false; }); }); describe('buildRequests', function () { - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); + it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; expect(serverRequest.method).to.exist; expect(serverRequest.url).to.exist; expect(serverRequest.data).to.exist; }); + it('Returns POST method', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { expect(serverRequest.url).to.equal('https://bes.mobfox.com/pbjs'); }); - it('Returns valid data if array of bids is valid', function () { + + it('Returns general data valid', function () { let data = serverRequest.data; expect(data).to.be.an('object'); - expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements'); + expect(data).to.have.all.keys('deviceWidth', + 'deviceHeight', + 'device', + 'language', + 'secure', + 'host', + 'page', + 'placements', + 'coppa', + 'ccpa', + 'gdpr', + 'tmax' + ); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.language).to.be.a('string'); expect(data.secure).to.be.within(0, 1); expect(data.host).to.be.a('string'); expect(data.page).to.be.a('string'); - expect(data.gdpr).to.not.exist; - expect(data.ccpa).to.not.exist; - let placement = data['placements'][0]; - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'sizes', 'schain', 'bidfloor'); - expect(placement.placementId).to.equal(783); - expect(placement.bidId).to.equal('23fhj33i987f'); - expect(placement.traffic).to.equal(BANNER); - expect(placement.schain).to.be.an('object'); - expect(placement.sizes).to.be.an('array'); - expect(placement.bidfloor).to.equal(0); + expect(data.coppa).to.be.a('number'); + expect(data.gdpr).to.be.a('object'); + expect(data.ccpa).to.be.a('string'); + expect(data.tmax).to.be.a('number'); + expect(data.placements).to.have.lengthOf(3); }); - it('Returns valid data for mediatype video', function () { - const playerSize = [300, 300]; - bid.mediaTypes = {}; - bid.params.traffic = VIDEO; - bid.mediaTypes[VIDEO] = { - playerSize - }; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'playerSize', '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 placements', function () { + const { placements } = serverRequest.data; + + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.placementId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid data for mediatype native', function () { - const native = { - title: { - required: true - }, - body: { - required: true - }, - icon: { - required: true, - size: [64, 64] + it('Returns valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids } - }; + ]; - bid.mediaTypes = {}; - bid.params.traffic = NATIVE; - bid.mediaTypes[NATIVE] = native; - serverRequest = spec.buildRequests([bid], bidderRequest); - let data = serverRequest.data; - expect(data).to.be.an('object'); - let placement = data['placements'][0]; - expect(placement).to.be.an('object'); - expect(placement).to.have.keys('placementId', 'bidId', 'traffic', 'native', 'schain', 'bidfloor'); - expect(placement.traffic).to.equal(NATIVE); - expect(placement.native).to.equal(native); + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + if (placement.adFormat === BANNER) { + expect(placement.sizes).to.be.an('array'); + } + switch (placement.traffic) { + case BANNER: + expect(placement.sizes).to.be.an('array'); + break; + case VIDEO: + expect(placement.playerSize).to.be.an('array'); + expect(placement.minduration).to.be.an('number'); + expect(placement.maxduration).to.be.an('number'); + break; + case NATIVE: + expect(placement.native).to.be.an('object'); + break; + } + } }); it('Returns data with gdprConsent and without uspConsent', function () { - bidderRequest.gdprConsent = 'test'; - serverRequest = spec.buildRequests([bid], bidderRequest); + delete bidderRequest.uspConsent; + serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); 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); + 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([]); - let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); }); describe('gpp consent', function () { @@ -152,7 +273,7 @@ describe('MobfoxHBBidAdapter', function () { applicableSections: [8] }; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); @@ -162,15 +283,18 @@ describe('MobfoxHBBidAdapter', function () { }) it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; bidderRequest.ortb2.regs.gpp = 'abc123'; bidderRequest.ortb2.regs.gpp_sid = [8]; - let serverRequest = spec.buildRequests([bid], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; expect(data).to.be.an('object'); expect(data).to.have.property('gpp'); expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; }) }); @@ -188,7 +312,11 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -196,15 +324,16 @@ describe('MobfoxHBBidAdapter', function () { 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('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.requestId).to.equal(banner.body[0].requestId); + expect(dataItem.cpm).to.equal(banner.body[0].cpm); + expect(dataItem.width).to.equal(banner.body[0].width); + expect(dataItem.height).to.equal(banner.body[0].height); + expect(dataItem.ad).to.equal(banner.body[0].ad); + expect(dataItem.ttl).to.equal(banner.body[0].ttl); + expect(dataItem.creativeId).to.equal(banner.body[0].creativeId); expect(dataItem.netRevenue).to.be.true; - expect(dataItem.currency).to.equal('USD'); + expect(dataItem.currency).to.equal(banner.body[0].currency); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret video response', function () { const video = { @@ -217,7 +346,11 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - dealId: '1' + dealId: '1', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -233,6 +366,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should interpret native response', function () { const native = { @@ -250,6 +384,10 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -269,6 +407,7 @@ describe('MobfoxHBBidAdapter', function () { expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; expect(dataItem.currency).to.equal('USD'); + expect(dataItem.meta).to.be.an('object').that.has.any.key('advertiserDomains'); }); it('Should return an empty array if invalid banner response is passed', function () { const invBanner = { diff --git a/test/spec/modules/mobianRtdProvider_spec.js b/test/spec/modules/mobianRtdProvider_spec.js new file mode 100644 index 00000000000..796a79e4e1c --- /dev/null +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -0,0 +1,245 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import * as ajax from 'src/ajax.js'; +import * as gptUtils from 'libraries/gptUtils/gptUtils.js'; +import { + CONTEXT_KEYS, + extendBidRequestConfig, + fetchContextData, + getConfig, + getContextData, + makeDataFromResponse, + setTargeting, +} from 'modules/mobianRtdProvider.js'; + +describe('Mobian RTD Submodule', function () { + let ajaxStub; + let bidReqConfig; + let setKeyValueSpy; + + const mockResponse = JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + ap: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, + mobianContentCategories: [], + mobianEmotions: ['affection'], + mobianGenres: [], + mobianRisk: 'low', + mobianSentiment: 'positive', + mobianThemes: [], + mobianTones: [], + } + }); + + const mockContextData = { + apValues: { a0: [], a1: [2313, 12], p0: [1231231, 212], p1: [231, 419] }, + categories: [], + emotions: ['affection'], + genres: [], + risk: 'low', + sentiment: 'positive', + themes: [], + tones: [], + } + + beforeEach(function () { + bidReqConfig = { + ortb2Fragments: { + global: { + site: { + ext: { + data: {} + } + } + } + } + }; + + setKeyValueSpy = sinon.spy(gptUtils, 'setKeyValue'); + }); + + afterEach(function () { + ajaxStub.restore(); + setKeyValueSpy.restore(); + }); + + describe('fetchContextData', function () { + it('should return fetched context data', async function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(mockResponse); + }); + + const contextData = await fetchContextData(); + expect(contextData).to.deep.equal(mockResponse); + }); + }); + + describe('makeDataFromResponse', function () { + it('should format context data response', async function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(mockResponse); + }); + + const data = makeDataFromResponse(mockResponse); + expect(data).to.deep.equal(mockContextData); + }); + }); + + describe('getContextData', function () { + it('should return formatted context data', async function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(mockResponse); + }); + + const data = await getContextData(); + expect(data).to.deep.equal(mockContextData); + }); + }); + + describe('setTargeting', function () { + it('should set targeting key-value pairs as per config', function () { + const parsedConfig = { + prefix: 'mobian', + publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + }; + setTargeting(parsedConfig, mockContextData); + + expect(setKeyValueSpy.callCount).to.equal(6); + expect(setKeyValueSpy.calledWith('mobian_ap_a1', [2313, 12])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p0', [1231231, 212])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_ap_p1', [231, 419])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_sentiment', 'positive')).to.equal(true); + + expect(setKeyValueSpy.calledWith('mobian_ap_a0')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_themes')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_tones')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_genres')).to.equal(false); + }); + + it('should not set key-value pairs if context data is empty', function () { + const parsedConfig = { + prefix: 'mobian', + publisherTargeting: ['apValues', 'emotions', 'risk', 'sentiment', 'themes', 'tones', 'genres'], + }; + setTargeting(parsedConfig, {}); + + expect(setKeyValueSpy.callCount).to.equal(0); + }); + + it('should only set key-value pairs for the keys specified in config', function () { + const parsedConfig = { + prefix: 'mobian', + publisherTargeting: ['emotions', 'risk'], + }; + + setTargeting(parsedConfig, mockContextData); + + expect(setKeyValueSpy.callCount).to.equal(2); + expect(setKeyValueSpy.calledWith('mobian_emotions', ['affection'])).to.equal(true); + expect(setKeyValueSpy.calledWith('mobian_risk', 'low')).to.equal(true); + + expect(setKeyValueSpy.calledWith('mobian_ap_a0')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_ap_a1')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_ap_p0')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_ap_p1')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_themes')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_tones')).to.equal(false); + expect(setKeyValueSpy.calledWith('mobian_genres')).to.equal(false); + }); + }); + + describe('extendBidRequestConfig', function () { + it('should extend bid request config with context data', function () { + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + }); + + it('should not override existing data', function () { + bidReqConfig.ortb2Fragments.global.site.ext.data = { + existing: 'data' + }; + + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal({ + existing: 'data', + ...mockContextData + }); + }); + + it('should create data object if missing', function () { + delete bidReqConfig.ortb2Fragments.global.site.ext.data; + const extendedConfig = extendBidRequestConfig(bidReqConfig, mockContextData); + expect(extendedConfig.ortb2Fragments.global.site.ext.data).to.deep.equal(mockContextData); + }); + }); + + describe('getConfig', function () { + it('should return config with correct keys', function () { + const config = getConfig({ + name: 'mobianBrandSafety', + params: { + prefix: 'mobiantest', + publisherTargeting: ['apValues'], + advertiserTargeting: ['emotions'], + } + }); + expect(config).to.deep.equal({ + prefix: 'mobiantest', + publisherTargeting: ['apValues'], + advertiserTargeting: ['emotions'], + }); + }); + + it('should set default values for configs not set', function () { + const config = getConfig({ + name: 'mobianBrandSafety', + params: { + publisherTargeting: ['apValues'], + } + }); + expect(config).to.deep.equal({ + prefix: 'mobian', + publisherTargeting: ['apValues'], + advertiserTargeting: [], + }); + }); + + it('should set default values if not provided', function () { + const config = getConfig({}); + expect(config).to.deep.equal({ + prefix: 'mobian', + publisherTargeting: [], + advertiserTargeting: [], + }); + }); + + it('should set default values if no config is provided', function () { + const config = getConfig(); + expect(config).to.deep.equal({ + prefix: 'mobian', + publisherTargeting: [], + advertiserTargeting: [], + }); + }); + + it('should set all tarteging values if value is true', function () { + const config = getConfig({ + name: 'mobianBrandSafety', + params: { + publisherTargeting: true, + advertiserTargeting: true, + } + }); + expect(config).to.deep.equal({ + prefix: 'mobian', + publisherTargeting: CONTEXT_KEYS, + advertiserTargeting: CONTEXT_KEYS, + }); + }); + }); +}); diff --git a/test/spec/modules/mytargetBidAdapter_spec.js b/test/spec/modules/mytargetBidAdapter_spec.js deleted file mode 100644 index 8880efd3d7c..00000000000 --- a/test/spec/modules/mytargetBidAdapter_spec.js +++ /dev/null @@ -1,199 +0,0 @@ -import { expect } from 'chai'; -import { spec } from 'modules/mytargetBidAdapter'; - -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: { - page: '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('//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); - }); - }); - - 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/nativoBidAdapter_spec.js b/test/spec/modules/nativoBidAdapter_spec.js index 75fb357b196..349051cb48e 100644 --- a/test/spec/modules/nativoBidAdapter_spec.js +++ b/test/spec/modules/nativoBidAdapter_spec.js @@ -221,6 +221,7 @@ describe('interpretResponse', function () { meta: { advertiserDomains: ['test.com'], }, + mediaType: 'banner', }, ] @@ -681,16 +682,24 @@ describe('hasProtocol', () => { describe('addProtocol', () => { it('www.testpage.com', () => { - expect(addProtocol('www.testpage.com')).to.be.equal('https://www.testpage.com') + expect(addProtocol('www.testpage.com')).to.be.equal( + 'https://www.testpage.com' + ) }) it('//www.testpage.com', () => { - expect(addProtocol('//www.testpage.com')).to.be.equal('https://www.testpage.com') + expect(addProtocol('//www.testpage.com')).to.be.equal( + 'https://www.testpage.com' + ) }) it('http://www.testpage.com', () => { - expect(addProtocol('http://www.testpage.com')).to.be.equal('http://www.testpage.com') + expect(addProtocol('http://www.testpage.com')).to.be.equal( + 'http://www.testpage.com' + ) }) it('https://www.testpage.com', () => { - expect(addProtocol('https://www.testpage.com')).to.be.equal('https://www.testpage.com') + expect(addProtocol('https://www.testpage.com')).to.be.equal( + 'https://www.testpage.com' + ) }) }) @@ -786,7 +795,7 @@ describe('RequestData', () => { describe('UserEIDs', () => { const userEids = new UserEIDs() - const eids = [{ 'testId': 1111 }] + const eids = [{ testId: 1111 }] describe('processBidRequestData', () => { it('Processes bid request without eids', () => { @@ -810,7 +819,7 @@ describe('UserEIDs', () => { expect(qs).to.include('ntv_pb_eid=') try { expect(JSON.parse(value)).to.be.equal(eids) - } catch (err) { } + } catch (err) {} }) }) }) @@ -828,12 +837,83 @@ describe('buildRequestUrl', () => { }) it('Returns baseUrl + QS params if QS strings passed', () => { - const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', 'ntv_foo=bar']) - expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + const url = buildRequestUrl(baseUrl, [ + 'ntv_ptd=123456&ntv_test=true', + 'ntv_foo=bar', + ]) + expect(url).to.be.equal( + `${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar` + ) }) it('Returns baseUrl + QS params if mixed QS strings passed', () => { - const url = buildRequestUrl(baseUrl, ['ntv_ptd=123456&ntv_test=true', '', '', 'ntv_foo=bar']) - expect(url).to.be.equal(`${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar`) + const url = buildRequestUrl(baseUrl, [ + 'ntv_ptd=123456&ntv_test=true', + '', + '', + 'ntv_foo=bar', + ]) + expect(url).to.be.equal( + `${baseUrl}?ntv_ptd=123456&ntv_test=true&ntv_foo=bar` + ) + }) +}) + +describe('Prebid Video', function () { + it('should handle video bid requests', function () { + const videoBidRequest = { + bidder: 'nativo', + params: { + video: { + mimes: ['video/mp4'], + protocols: [2, 3, 5, 6], + playbackmethod: [1, 2], + skip: 1, + skipafter: 5, + }, + }, + } + + const isValid = spec.isBidRequestValid(videoBidRequest) + expect(isValid).to.be.true + }) +}) + +describe('Prebid Native', function () { + it('should handle native bid requests', function () { + const nativeBidRequest = { + bidder: 'nativo', + params: { + native: { + title: { + required: true, + len: 80, + }, + image: { + required: true, + sizes: [150, 50], + }, + sponsoredBy: { + required: true, + }, + clickUrl: { + required: true, + }, + privacyLink: { + required: false, + }, + body: { + required: true, + }, + icon: { + required: true, + sizes: [50, 50], + }, + }, + }, + } + + const isValid = spec.isBidRequestValid(nativeBidRequest) + expect(isValid).to.be.true }) }) diff --git a/test/spec/modules/naveggIdSystem_spec.js b/test/spec/modules/naveggIdSystem_spec.js index 2c4f1cda859..4907a63abde 100644 --- a/test/spec/modules/naveggIdSystem_spec.js +++ b/test/spec/modules/naveggIdSystem_spec.js @@ -1,45 +1,155 @@ -import { naveggIdSubmodule, storage } from 'modules/naveggIdSystem.js'; +import { naveggIdSubmodule, storage, getIdFromAPI } from 'modules/naveggIdSystem.js'; +import { server } from 'test/mocks/xhr.js'; +import * as ajaxLib from 'src/ajax.js'; -describe('naveggId', function () { - let sandbox; - beforeEach(() => { - sandbox = sinon.sandbox.create(); - sandbox.stub(storage, 'getDataFromLocalStorage'); +const NAVEGGID_CONFIG_COOKIE_HTML5 = { + storage: { + name: 'nvggid', + type: 'cookie&html5', + expires: 8 + } +} + +const MOCK_RESPONSE = { + nvggid: 'test_nvggid' +} + +const MOCK_RESPONSE_NULL = { + nvggid: null +} + +function mockResponse(responseText, isSuccess = true) { + return function(url, callbacks) { + if (isSuccess) { + callbacks.success(responseText) + } else { + callbacks.error(new Error('Mock Error')) + } + } +} + +function deleteAllCookies() { + document.cookie.split(';').forEach(cookie => { + const eqPos = cookie.indexOf('='); + const name = eqPos > -1 ? cookie.substring(0, eqPos) : cookie; + document.cookie = name + '=;expires=Thu, 01 Jan 1970 00:00:00 GMT'; + }); +} + +function setLocalStorage() { + storage.setDataInLocalStorage('nvggid', 'localstorage_value'); +} + +describe('getId', function () { + let ajaxStub, ajaxBuilderStub; + + beforeEach(function() { + ajaxStub = sinon.stub(); + ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').returns(ajaxStub); + }); + + afterEach(function() { + ajaxBuilderStub.restore(); + deleteAllCookies(); + storage.removeDataFromLocalStorage('nvggid'); + }); + + it('should get the value from the existing localstorage', function() { + setLocalStorage(); + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('localstorage_value')).to.be.true; }); - afterEach(() => { - sandbox.restore(); + + it('should get the value from a nid cookie', function() { + storage.setCookie('nid', 'old_nid_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nid_cookie')).to.be.true; }); - it('should NOT find navegg id', function () { - let id = naveggIdSubmodule.getId(); + it('should get the value from a nav cookie', function() { + storage.setCookie('navId', 'old_nav_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - expect(id).to.be.undefined; + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nav_cookie')).to.be.true; }); - 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('should get the value from an old nvg cookie', function() { + storage.setCookie('nvgid', 'old_nvg_cookie', storage.expires) + + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; + + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) - it('getId() should return "test-nvggid" id from local storage NAVEGG_ID', function() { - storage.getDataFromLocalStorage.callsFake(() => 'test-ninvggidd') + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('old_nvg_cookie')).to.be.true; + }); - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'test-ninvggidd'}) - }) + it('should return correct value from API response', function(done) { + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - it('getId() should return "test-nvggid" id from local storage NAV0', function() { - storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nav0') + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE)); + } + }); + apiCallback(callback) - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'nvgid-nav0'}) - }) + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith('test_nvggid')).to.be.true; + done(); + }); - it('getId() should return "test-nvggid" id from local storage NVG0', function() { - storage.getDataFromLocalStorage.callsFake(() => 'nvgid-nvg0') + it('should return no value from API response', function(done) { + const callback = sinon.spy(); + const apiCallback = naveggIdSubmodule.getId(NAVEGGID_CONFIG_COOKIE_HTML5).callback; - let id = naveggIdSubmodule.getId(); - expect(id).to.be.deep.equal({id: 'nvgid-nvg0'}) - }) + ajaxStub.callsFake((url, successCallbacks, errorCallback, options) => { + if (successCallbacks && typeof successCallbacks === 'function') { + successCallbacks(JSON.stringify(MOCK_RESPONSE_NULL)); + } + }); + apiCallback(callback) + expect(callback.calledOnce).to.be.true; + expect(callback.calledWith(undefined)).to.be.true; + done(); + }); }); diff --git a/test/spec/modules/netIdSystem_spec.js b/test/spec/modules/netIdSystem_spec.js new file mode 100644 index 00000000000..bbf59c39f32 --- /dev/null +++ b/test/spec/modules/netIdSystem_spec.js @@ -0,0 +1,23 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {netIdSubmodule} from '../../../modules/netIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; + +describe('Net ID', () => { + describe('eid', () => { + before(() => { + attachIdSystem(netIdSubmodule); + }); + it('NetId', function () { + const userId = { + netId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'netid.de', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }); +}); diff --git a/test/spec/modules/nextMillenniumBidAdapter_spec.js b/test/spec/modules/nextMillenniumBidAdapter_spec.js index ff58671b17b..a5c5f5f714d 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -1,6 +1,7 @@ import { expect } from 'chai'; import { getImp, + getSourceObj, replaceUsersyncMacros, setConsentStrings, setOrtb2Parameters, @@ -15,9 +16,11 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - banner', data: { id: '123', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {banner: {sizes: [[300, 250], [320, 250]]}}, adUnitCode: 'test-banner-1', + bidId: 'e36ea395f67f', }, mediaTypes: { @@ -30,7 +33,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'test-banner-1', + id: 'e36ea395f67f', bidfloorcur: 'EUR', bidfloor: 1.11, ext: {prebid: {storedrequest: {id: '123'}}}, @@ -42,9 +45,11 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - video', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {playerSize: [400, 300], api: [2], placement: 1, plcmt: 1}}, adUnitCode: 'test-video-1', + bidId: 'e36ea395f67f', }, mediaTypes: { @@ -56,7 +61,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'test-video-1', + id: 'e36ea395f67f', bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, video: { @@ -74,9 +79,10 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'imp - mediaTypes.video is empty', data: { id: '234', + postBody: {ext: {nextMillennium: {refresh_counts: {}, elemOffsets: {}}}}, bid: { mediaTypes: {video: {w: 640, h: 480}}, - adUnitCode: 'test-video-2', + bidId: 'e36ea395f67f', }, mediaTypes: { @@ -88,7 +94,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - id: 'test-video-2', + id: 'e36ea395f67f', bidfloorcur: 'USD', ext: {prebid: {storedrequest: {id: '234'}}}, video: {w: 640, h: 480, mimes: ['video/mp4', 'video/x-ms-wmv', 'application/javascript']}, @@ -98,13 +104,119 @@ describe('nextMillenniumBidAdapterTests', () => { for (let {title, data, expected} of dataTests) { it(title, () => { - const {bid, id, mediaTypes} = data; - const imp = getImp(bid, id, mediaTypes); + const {bid, id, mediaTypes, postBody} = data; + const imp = getImp(bid, id, mediaTypes, postBody); expect(imp).to.deep.equal(expected); }); } }); + describe('function getSourceObj', () => { + const dataTests = [ + { + title: 'schain is empty', + validBidRequests: [{}], + bidderRequest: {}, + expected: undefined, + }, + + { + title: 'schain is validBidReequest', + bidderRequest: {}, + validBidRequests: [{ + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }], + + expected: { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }, + }, + + { + title: 'schain is bidderReequest.ortb2.source.schain', + bidderRequest: { + ortb2: { + source: { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }, + }, + }, + + validBidRequests: [{}], + expected: { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }, + }, + + { + title: 'schain is bidderReequest.ortb2.source.ext.schain', + bidderRequest: { + ortb2: { + source: { + ext: { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }, + }, + }, + }, + + validBidRequests: [{}], + expected: { + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [{asi: 'test.test', sid: '00001', hp: 1}], + }, + }, + }, + }, + ]; + + for (let {title, validBidRequests, bidderRequest, expected} of dataTests) { + it(title, () => { + const source = getSourceObj(validBidRequests, bidderRequest); + expect(source).to.deep.equal(expected); + }); + } + }); + describe('function setConsentStrings', () => { const dataTests = [ { @@ -115,16 +227,18 @@ describe('nextMillenniumBidAdapterTests', () => { uspConsent: '1---', gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, gdprConsent: {consentString: 'kjfdniwjnifwenrif3', gdprApplies: true}, - ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}}, + ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10], coppa: 1}}, }, }, expected: { - user: {ext: {consent: 'kjfdniwjnifwenrif3'}}, + user: {consent: 'kjfdniwjnifwenrif3'}, regs: { gpp: 'DBACNYA~CPXxRfAPXxR', gpp_sid: [7], - ext: {gdpr: 1, us_privacy: '1---'}, + gdpr: 1, + us_privacy: '1---', + coppa: 1 }, }, }, @@ -135,16 +249,17 @@ describe('nextMillenniumBidAdapterTests', () => { postBody: {}, bidderRequest: { gdprConsent: {consentString: 'ewtewbefbawyadexv', gdprApplies: false}, - ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10]}}, + ortb2: {regs: {gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10], coppa: 0}}, }, }, expected: { - user: {ext: {consent: 'ewtewbefbawyadexv'}}, + user: {consent: 'ewtewbefbawyadexv'}, regs: { gpp: 'DSFHFHWEUYVDC', gpp_sid: [8, 9, 10], - ext: {gdpr: 0}, + gdpr: 0, + coppa: 0, }, }, }, @@ -157,7 +272,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { - regs: {ext: {gdpr: 0}}, + regs: {gdpr: 0}, }, }, @@ -411,16 +526,27 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'site.pagecat, site.content.cat and site.content.language', data: { postBody: {}, - ortb2: {site: { + ortb2: { + bcat: ['IAB1-3', 'IAB1-4'], + badv: ['domain1.com', 'domain2.com'], + wlang: ['en', 'fr', 'de'], + wlangb: ['en', 'fr', 'de'], + site: { + pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], + content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'}, + } + }, + }, + + expected: { + bcat: ['IAB1-3', 'IAB1-4'], + badv: ['domain1.com', 'domain2.com'], + wlang: ['en', 'fr', 'de'], + site: { pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'}, - }}, + } }, - - expected: {site: { - pagecat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], - content: {cat: ['IAB2-11', 'IAB2-12', 'IAB2-14'], language: 'EN'}, - }}, }, { @@ -428,6 +554,7 @@ describe('nextMillenniumBidAdapterTests', () => { data: { postBody: {}, ortb2: { + wlangb: ['en', 'fr', 'de'], user: {keywords: 'key7,key8,key9'}, site: { keywords: 'key1,key2,key3', @@ -437,6 +564,7 @@ describe('nextMillenniumBidAdapterTests', () => { }, expected: { + wlangb: ['en', 'fr', 'de'], user: {keywords: 'key7,key8,key9'}, site: { keywords: 'key1,key2,key3', @@ -485,9 +613,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: undefined, - }, + }], }, expected: {}, @@ -497,9 +625,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids - array is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: [], - }, + }], }, expected: {}, @@ -509,19 +637,34 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is', data: { postBody: {}, - bid: { - userIdAsEids: [ - { - source: '33across.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, + bids: [ + { + userIdAsEids: [], + }, - { - source: 'utiq.com', - uids: [{id: 'some-random-id-value', atype: 1}], - }, - ], - }, + { + userIdAsEids: [ + { + source: '33across.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + + { + source: 'utiq.com', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + + { + userIdAsEids: [ + { + source: 'test.test', + uids: [{id: 'some-random-id-value', atype: 1}], + }, + ], + }, + ], }, expected: { @@ -544,273 +687,145 @@ describe('nextMillenniumBidAdapterTests', () => { for (let { title, data, expected } of dataTests) { it(title, () => { - const { postBody, bid } = data; - setEids(postBody, bid); + const { postBody, bids } = data; + setEids(postBody, bids); expect(postBody).to.deep.equal(expected); }); } }); - const bidRequestData = [{ - adUnitCode: 'test-div', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { placement_id: '-1' }, - sizes: [[300, 250]], - uspConsent: '1---', - gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - }, - - ortb2: { - device: { - w: 1500, - h: 1000 - }, - - site: { - domain: 'example.com', - page: 'http://example.com' - } - } - }]; - - const serverResponse = { - body: { - 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', - ext: { - sync: { - image: ['urlA?gdpr={{.GDPR}}&gpp={{.GPP}}&gpp_sid={{.GPPSID}}'], - iframe: ['urlB'], - } - } - } - }; - - const bidRequestDataGI = [ - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250]] - } - }, + const bidRequestDataGI = getBidRequestDataGI(); + function getBidRequestDataGI(adUnitCodes = ['test-banner-gi', 'test-banner-gi', 'test-video-gi']) { + return [ + { + adUnitCode: adUnitCodes[0], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250]] + } + }, - sizes: [[300, 250]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - - { - adUnitCode: 'test-banner-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - banner: { - sizes: [[300, 250], [300, 300]] + sizes: [[300, 250]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250], [300, 300]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - - { - adUnitCode: 'test-video-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - video: { - playerSize: [640, 480], + { + adUnitCode: adUnitCodes[1], + bidId: 'bid1235', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, + + sizes: [[300, 250], [300, 300]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - ]; + { + adUnitCode: adUnitCodes[2], + bidId: 'bid1236', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + video: { + playerSize: [640, 480], + } + }, - it('validate_generated_params', function() { - const request = spec.buildRequests(bidRequestData, {bidderRequestId: 'mock-uuid'}); - expect(request[0].bidId).to.equal('bid1234'); - expect(JSON.parse(request[0].data).id).to.exist; - }); + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + } + }, + ]; + } - it('use parameters group_id', function() { + describe('check parameters group_id or placement_id', function() { + let numberTest = 0 for (let test of bidRequestDataGI) { - const request = spec.buildRequests([test]); - const requestData = JSON.parse(request[0].data); - const storeRequestId = requestData.ext.prebid.storedrequest.id; - const templateRE = /^g[1-9]\d*;(?:[1-9]\d*x[1-9]\d*\|)*[1-9]\d*x[1-9]\d*;/; - expect(templateRE.test(storeRequestId)).to.be.true; - }; - }); - - it('Check if refresh_count param is incremented', function() { - const request = spec.buildRequests(bidRequestData); - expect(JSON.parse(request[0].data).ext.nextMillennium.refresh_count).to.equal(1); - }); - - it('Check if domain was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).site.domain).to.exist - }) - - it('Check if elOffsets was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).ext.nextMillennium.elOffsets).to.be.an('object') - }) - - it('Check if imp object was added', function() { - const request = spec.buildRequests(bidRequestData) - expect(JSON.parse(request[0].data).imp).to.be.an('array') - }); - - it('Check if imp prebid stored id is correct', function() { - const request = spec.buildRequests(bidRequestData) - const requestData = JSON.parse(request[0].data); - const storedReqId = requestData.ext.prebid.storedrequest.id; - expect(requestData.imp[0].ext.prebid.storedrequest.id).to.equal(storedReqId) - }); - - it('validate_response_params', function() { - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('96846035'); - expect(bid.ad).to.equal('Hello! It\'s a test ad!'); - 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(`test - ${++numberTest}`, () => { + const request = spec.buildRequests([test]); + const requestData = JSON.parse(request[0].data); + const storeRequestId = (requestData.imp[0].ext.prebid.storedrequest.id || ''); + expect(storeRequestId.length).to.be.not.equal(0); + + const srId = storeRequestId.split(';'); + const isGroupId = (/^g[1-9]\d*/).test(srId[0]); + if (isGroupId) { + expect(srId.length).to.be.equal(3); + expect((/^g[1-9]\d*/).test(srId[0])).to.be.true; + const sizes = srId[1].split('|'); + for (let size of sizes) { + if (!(/^[1-9]\d*[xX,][1-9]\d*$/).test(size)) { + expect(storeRequestId).to.be.equal(''); + } - it('validate_videowrapper_response_params', function() { - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: 'https://some_vast_host.com/vast.xml', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - ext: { - prebid: { - type: 'video' - } - } - } - ] + expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; } - ], - cur: 'USD' - } + } else { + expect(srId.length).to.be.equal(1); + expect((/^[1-9]\d*/).test(srId[0])).to.be.true; + }; + }); }; - - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); - - let bid = bids[0]; - - expect(bid.creativeId).to.equal('96846035'); - expect(bid.vastUrl).to.equal('https://some_vast_host.com/vast.xml'); - 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_videoxml_response_params', function() { - const serverResponse = { - body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', - seatbid: [ - { - bid: [ - { - id: '7457329903666272789', - price: 0.5, - adm: '', - adid: '96846035', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - ext: { - prebid: { - type: 'video' - } - } - } - ] - } - ], - cur: 'USD' - } - }; + describe('Check ext.next_mil_imps', function() { + const expectedNextMilImps = [ + { + impId: 'bid1234', + nextMillennium: {refresh_count: 1}, + }, - let bids = spec.interpretResponse(serverResponse, bidRequestData[0]); - expect(bids).to.have.lengthOf(1); + { + impId: 'bid1235', + nextMillennium: {refresh_count: 1}, + }, - let bid = bids[0]; + { + impId: 'bid1236', + nextMillennium: {refresh_count: 1}, + }, + ]; - expect(bid.creativeId).to.equal('96846035'); - expect(bid.vastXml).to.equal(''); - 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'); + const dataForRequest = getBidRequestDataGI(expectedNextMilImps.map(el => el.impId)); + for (let j = 0; j < 2; j++) { + const request = spec.buildRequests(dataForRequest); + const bidRequest = JSON.parse(request[0].data); + for (let i = 0; i < bidRequest.ext.next_mil_imps.length; i++) { + it(`test - ${j * i + 1}`, () => { + const nextMilImp = bidRequest.ext.next_mil_imps[i]; + expect(nextMilImp.impId).to.deep.equal(expectedNextMilImps[i].impId); + expect(nextMilImp.nextMillennium.refresh_count).to.deep.equal(expectedNextMilImps[i].nextMillennium.refresh_count + j); + }) + }; + }; }); - it('Check function of getting URL for sending statistics data', function() { + describe('function spec._getUrlPixelMetric', function() { const dataForTests = [ { + title: 'Check function of getting URL for sending statistics data - 1', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {}}], }, @@ -819,8 +834,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 2', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'appnexus', bids: [{bidder: 'appnexus', params: {placement_id: '807'}}], }, @@ -829,8 +845,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 3', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807'}}], }, @@ -839,8 +856,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 4', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807'}}, @@ -852,8 +870,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 5', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [{bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}], }, @@ -862,8 +881,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 6', eventName: 'bidRequested', - bid: { + bids: { bidderCode: 'nextMillennium', bids: [ {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, @@ -876,8 +896,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 7', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'appnexus', }, @@ -885,8 +906,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 8', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'nextMillennium', params: {placement_id: '807'}, }, @@ -895,8 +917,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 9', eventName: 'noBid', - bid: { + bids: { bidder: 'appnexus', }, @@ -904,8 +927,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 10', eventName: 'noBid', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, @@ -914,8 +938,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 11', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'appnexus', }, @@ -923,19 +948,250 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 12', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'nextMillennium', params: {placement_id: '807'}, }, expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidTimeout&bidder=nextMillennium&source=pbjs&placements=807', }, + + { + title: 'Check function of getting URL for sending statistics data - 13', + eventName: 'bidRequested', + bids: [ + { + bidderCode: 'nextMillennium', + bids: [ + {bidder: 'nextMillennium', params: {placement_id: '807', group_id: '123'}}, + {bidder: 'nextMillennium', params: {group_id: '456'}}, + {bidder: 'nextMillennium', params: {placement_id: '222'}}, + ], + }, + + { + bidderCode: 'nextMillennium', + params: {group_id: '7777'}, + }, + + { + bidderCode: 'nextMillennium', + params: {placement_id: '8888'}, + }, + ], + + expected: 'https://report2.hb.brainlyads.com/statistics/metric?event=bidRequested&bidder=nextMillennium&source=pbjs&groups=123;456;7777&placements=222;8888', + }, + ]; + + for (let {title, eventName, bids, expected} of dataForTests) { + it(title, () => { + const url = spec._getUrlPixelMetric(eventName, bids); + expect(url).to.equal(expected); + }); + }; + }); + + describe('check function buildRequests', () => { + const tests = [ + { + title: 'test - 1', + bidderRequest: {bidderRequestId: 'mock-uuid', timeout: 1200}, + bidRequests: [ + { + adUnitCode: 'test-div', + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '-1' }, + sizes: [[300, 250]], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + }, + + ortb2: { + device: { + w: 1500, + h: 1000 + }, + + site: { + domain: 'example.com', + page: 'http://example.com' + } + } + }, + + { + adUnitCode: 'test-div-2', + bidId: 'bid1235', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { placement_id: '333' }, + sizes: [[300, 250]], + uspConsent: '1---', + gppConsent: {gppString: 'DBACNYA~CPXxRfAPXxR', applicableSections: [7]}, + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true + }, + + ortb2: { + device: { + w: 1500, + h: 1000 + }, + + site: { + domain: 'example.com', + page: 'http://example.com' + } + }, + }, + ], + + expected: { + id: 'mock-uuid', + impSize: 2, + requestSize: 1, + domain: 'example.com', + tmax: 1200, + }, + }, ]; - for (let {eventName, bid, expected} of dataForTests) { - const url = spec._getUrlPixelMetric(eventName, bid); - expect(url).to.equal(expected); + for (let {title, bidRequests, bidderRequest, expected} of tests) { + it(title, () => { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.length).to.equal(expected.requestSize); + + const requestData = JSON.parse(request[0].data); + expect(requestData.id).to.equal(expected.id); + expect(requestData.tmax).to.equal(expected.tmax); + expect(requestData?.imp?.length).to.equal(expected.impSize); + }); + }; + }); + + describe('check function interpretResponse', () => { + const tests = [ + { + title: 'test - 1', + serverResponse: { + body: { + id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', + seatbid: [ + { + bid: [ + { + id: '7457329903666272789-0', + impid: '700ce0a43f72', + price: 0.5, + adm: 'Hello! It\'s a test ad!', + adid: '96846035-0', + adomain: ['test.addomain.com'], + w: 300, + h: 250, + }, + + { + id: '7457329903666272789-1', + impid: '700ce0a43f73', + price: 0.7, + adm: 'https://some_vast_host.com/vast.xml', + adid: '96846035-1', + adomain: ['test.addomain.com'], + w: 400, + h: 300, + ext: {prebid: {type: 'video'}}, + }, + + { + id: '7457329903666272789-2', + impid: '700ce0a43f74', + price: 1.0, + adm: '', + adid: '96846035-3', + adomain: ['test.addomain.com'], + w: 640, + h: 480, + ext: {prebid: {type: 'video'}}, + }, + ], + }, + ], + cur: 'USD', + }, + }, + + expected: [ + { + title: 'banner', + requestId: '700ce0a43f72', + creativeId: '96846035-0', + ad: 'Hello! It\'s a test ad!', + vastUrl: undefined, + vastXml: undefined, + cpm: 0.5, + width: 300, + height: 250, + currency: 'USD', + }, + + { + title: 'video - vastUrl', + requestId: '700ce0a43f73', + creativeId: '96846035-1', + ad: undefined, + vastUrl: 'https://some_vast_host.com/vast.xml', + vastXml: undefined, + cpm: 0.7, + width: 400, + height: 300, + currency: 'USD', + }, + + { + title: 'video - vastXml', + requestId: '700ce0a43f74', + creativeId: '96846035-3', + ad: undefined, + vastUrl: undefined, + vastXml: '', + cpm: 1.0, + width: 640, + height: 480, + currency: 'USD', + }, + ], + }, + ]; + + for (let {title, serverResponse, bidRequest, expected} of tests) { + describe(title, () => { + const bids = spec.interpretResponse(serverResponse, bidRequest); + for (let i = 0; i < bids.length; i++) { + it(expected[i].title, () => { + expect(bids).to.have.lengthOf(expected.length); + + const bid = bids[i] + expect(bid.creativeId).to.equal(expected[i].creativeId); + expect(bid.requestId).to.equal(expected[i].requestId); + expect(bid.ad).to.equal(expected[i].ad); + expect(bid.vastUrl).to.equal(expected[i].vastUrl); + expect(bid.vastXml).to.equal(expected[i].vastXml); + expect(bid.cpm).to.equal(expected[i].cpm); + expect(bid.width).to.equal(expected[i].width); + expect(bid.height).to.equal(expected[i].height); + expect(bid.currency).to.equal(expected[i].currency); + }); + }; + }); }; - }) + }); }); diff --git a/test/spec/modules/nexx360BidAdapter_spec.js b/test/spec/modules/nexx360BidAdapter_spec.js index f18e0365226..48fa0e2a021 100644 --- a/test/spec/modules/nexx360BidAdapter_spec.js +++ b/test/spec/modules/nexx360BidAdapter_spec.js @@ -3,6 +3,7 @@ import { spec, storage, getNexx360LocalStorage, } from 'modules/nexx360BidAdapter.js'; import { sandbox } from 'sinon'; +import { getAmxId } from '../../../modules/nexx360BidAdapter'; const instreamResponse = { 'id': '2be64380-ba0c-405a-ab53-51f51c7bde51', @@ -220,7 +221,7 @@ describe('Nexx360 bid adapter tests', function () { after(function () { sandbox.restore() }); - }) + }); describe('getNexx360LocalStorage enabled', function () { before(function () { @@ -235,7 +236,37 @@ describe('Nexx360 bid adapter tests', function () { after(function () { sandbox.restore() }); - }) + }); + + describe('getAmxId() with localStorage enabled and data not set', function() { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => null); + }); + it('We test if we get the amxId', function() { + const output = getAmxId(); + expect(output).to.be.eql(false); + }); + after(function () { + sandbox.restore() + }); + }); + + describe('getAmxId() with localStorage enabled and data set', function() { + before(function () { + sandbox.stub(storage, 'localStorageIsEnabled').callsFake(() => true); + sandbox.stub(storage, 'setDataInLocalStorage'); + sandbox.stub(storage, 'getDataFromLocalStorage').callsFake((key) => 'abcdef'); + }); + it('We test if we get the amxId', function() { + const output = getAmxId(); + expect(output).to.be.eql('abcdef'); + }); + after(function () { + sandbox.restore() + }); + }); describe('buildRequests()', function() { before(function () { @@ -374,7 +405,7 @@ describe('Nexx360 bid adapter tests', function () { expect(requestContent.imp[1].tagid).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.adUnitCode).to.be.eql('div-2-abcd'); expect(requestContent.imp[1].ext.divId).to.be.eql('div-2-abcd'); - expect(requestContent.ext.bidderVersion).to.be.eql('4.0'); + expect(requestContent.ext.bidderVersion).to.be.eql('4.2'); expect(requestContent.ext.source).to.be.eql('prebid.js'); }); diff --git a/test/spec/modules/oguryBidAdapter_spec.js b/test/spec/modules/oguryBidAdapter_spec.js index ea923a18e57..99c16d0b1af 100644 --- a/test/spec/modules/oguryBidAdapter_spec.js +++ b/test/spec/modules/oguryBidAdapter_spec.js @@ -13,6 +13,11 @@ describe('OguryBidAdapter', function () { bidRequests = [ { adUnitCode: 'adUnitCode', + ortb2Imp: { + ext: { + gpid: 'gpid' + } + }, auctionId: 'auctionId', bidId: 'bidId', bidder: 'ogury', @@ -80,6 +85,7 @@ describe('OguryBidAdapter', function () { bidderRequestId: 'mock-uuid', auctionId: bidRequests[0].auctionId, gdprConsent: {consentString: 'myConsentString', vendorData: {}, gdprApplies: true}, + gppConsent: {gppString: 'myGppString', gppData: {}, applicableSections: [7], parsedSections: {}}, timeout: 1000 }; @@ -126,13 +132,17 @@ describe('OguryBidAdapter', function () { }); describe('getUserSyncs', function() { - let syncOptions, gdprConsent; + let syncOptions, gdprConsent, gppConsent; beforeEach(() => { gdprConsent = { gdprApplies: true, consentString: 'CPJl4C8PJl4C8OoAAAENAwCMAP_AAH_AAAAAAPgAAAAIAPgAAAAIAAA.IGLtV_T9fb2vj-_Z99_tkeYwf95y3p-wzhheMs-8NyZeH_B4Wv2MyvBX4JiQKGRgksjLBAQdtHGlcTQgBwIlViTLMYk2MjzNKJrJEilsbO2dYGD9Pn8HT3ZCY70-vv__7v3ff_3g' }; + gppConsent = { + gppString: 'DBABLA~BAAAAAAAAQA.QA', + applicableSections: [7] + } }); describe('pixel', () => { @@ -141,7 +151,7 @@ describe('OguryBidAdapter', function () { }); it('should return syncs array with three elements of type image', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); @@ -153,22 +163,34 @@ describe('OguryBidAdapter', function () { }); it('should set the source as query param', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs[0].url).to.contain('source=prebid'); - expect(userSyncs[1].url).to.contain('source=prebid'); - expect(userSyncs[2].url).to.contain('source=prebid'); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('source')).to.equal('prebid') }); it('should set the tcString as query param', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs[0].url).to.contain(`iab_string=${gdprConsent.consentString}`); - expect(userSyncs[1].url).to.contain(`iab_string=${gdprConsent.consentString}`); - expect(userSyncs[2].url).to.contain(`iab_string=${gdprConsent.consentString}`); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal(gdprConsent.consentString) + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal(gdprConsent.consentString) + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal(gdprConsent.consentString) + }); + + it('should set the gppString as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('gpp')).to.equal(gppConsent.gppString) + expect(new URL(userSyncs[1].url).searchParams.get('gpp')).to.equal(gppConsent.gppString) + expect(new URL(userSyncs[2].url).searchParams.get('gpp')).to.equal(gppConsent.gppString) + }); + + it('should set the gpp_sid as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + expect(new URL(userSyncs[1].url).searchParams.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + expect(new URL(userSyncs[2].url).searchParams.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) }); it('should return an empty array when pixel is disable', () => { syncOptions.pixelEnabled = false; - expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); + expect(spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent)).to.have.lengthOf(0); }); it('should return syncs array with three elements of type image when consentString is undefined', () => { @@ -177,14 +199,14 @@ describe('OguryBidAdapter', function () { consentString: undefined }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') }); it('should return syncs array with three elements of type image when consentString is null', () => { @@ -193,40 +215,40 @@ describe('OguryBidAdapter', function () { consentString: null }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is undefined', () => { gdprConsent = undefined; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is null', () => { gdprConsent = null; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is null and gdprApplies is false', () => { @@ -235,14 +257,14 @@ describe('OguryBidAdapter', function () { consentString: null }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') }); it('should return syncs array with three elements of type image when gdprConsent is empty string and gdprApplies is false', () => { @@ -251,14 +273,158 @@ describe('OguryBidAdapter', function () { consentString: '' }; + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(new URL(userSyncs[0].url).searchParams.get('iab_string')).to.equal('') + expect(userSyncs[1].type).to.equal('image'); + expect(new URL(userSyncs[1].url).searchParams.get('iab_string')).to.equal('') + expect(userSyncs[2].type).to.equal('image'); + expect(new URL(userSyncs[2].url).searchParams.get('iab_string')).to.equal('') + }); + + it('should return syncs array with three elements of type image when gppString is undefined', () => { + gppConsent = { + applicableSections: [7], + gppString: undefined + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[2].type).to.equal('image'); + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + }); + + it('should return syncs array with three elements of type image when gppString is null', () => { + gppConsent = { + applicableSections: [7, 8], + gppString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[2].type).to.equal('image'); + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + }); + + it('should return syncs array with three elements of type image when gppConsent is undefined', () => { + gppConsent = undefined; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[2].type).to.equal('image'); + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal('') + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal('') + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with three elements of type image when gppConsent is null', () => { + gppConsent = null; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[2].type).to.equal('image'); + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal('') + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal('') + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with three elements of type image when gppConsent is null and applicableSections is empty', () => { + gppConsent = { + applicableSections: [], + gppString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(3); + expect(userSyncs[0].type).to.equal('image'); + expect(userSyncs[1].type).to.equal('image'); + expect(userSyncs[2].type).to.equal('image'); + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal('') + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal('') + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with three elements of type image when gppString is empty string and applicableSections is empty', () => { + gppConsent = { + applicableSections: [], + gppString: '' + }; + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); expect(userSyncs).to.have.lengthOf(3); expect(userSyncs[0].type).to.equal('image'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/v1/init-sync/bid-switch?iab_string=&source=prebid') expect(userSyncs[1].type).to.equal('image'); - expect(userSyncs[1].url).to.equal('https://ms-cookie-sync.presage.io/ttd/init-sync?iab_string=&source=prebid') expect(userSyncs[2].type).to.equal('image'); - expect(userSyncs[2].url).to.equal('https://ms-cookie-sync.presage.io/xandr/init-sync?iab_string=&source=prebid') + + const firstUrlSync = new URL(userSyncs[0].url).searchParams + expect(firstUrlSync.get('gpp')).to.equal('') + expect(firstUrlSync.get('gpp_sid')).to.equal('') + + const secondtUrlSync = new URL(userSyncs[1].url).searchParams + expect(secondtUrlSync.get('gpp')).to.equal('') + expect(secondtUrlSync.get('gpp_sid')).to.equal('') + + const thirdUrlSync = new URL(userSyncs[2].url).searchParams + expect(thirdUrlSync.get('gpp')).to.equal('') + expect(thirdUrlSync.get('gpp_sid')).to.equal('') }); }); @@ -268,7 +434,7 @@ describe('OguryBidAdapter', function () { }); it('should return syncs array with one element of type iframe', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); @@ -276,18 +442,23 @@ describe('OguryBidAdapter', function () { }); it('should set the source as query param', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs[0].url).to.contain('source=prebid'); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('source')).to.equal('prebid'); }); it('should set the tcString as query param', () => { - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); - expect(userSyncs[0].url).to.contain(`gdpr_consent=${gdprConsent.consentString}`); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(gdprConsent.consentString); + }); + + it('should set the gppString as query param', () => { + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(new URL(userSyncs[0].url).searchParams.get('gpp')).to.equal(gppConsent.gppString); }); it('should return an empty array when iframe is disable', () => { syncOptions.iframeEnabled = false; - expect(spec.getUserSyncs(syncOptions, [], gdprConsent)).to.have.lengthOf(0); + expect(spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent)).to.have.lengthOf(0); }); it('should return syncs array with one element of type iframe when consentString is undefined', () => { @@ -296,10 +467,10 @@ describe('OguryBidAdapter', function () { consentString: undefined }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); }); it('should return syncs array with one element of type iframe when consentString is null', () => { @@ -308,28 +479,28 @@ describe('OguryBidAdapter', function () { consentString: null }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); }); it('should return syncs array with one element of type iframe when gdprConsent is undefined', () => { gdprConsent = undefined; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); }); it('should return syncs array with one element of type iframe when gdprConsent is null', () => { gdprConsent = null; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); }); it('should return syncs array with one element of type iframe when gdprConsent is null and gdprApplies is false', () => { @@ -338,10 +509,10 @@ describe('OguryBidAdapter', function () { consentString: null }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); }); it('should return syncs array with one element of type iframe when gdprConsent is empty string and gdprApplies is false', () => { @@ -350,10 +521,93 @@ describe('OguryBidAdapter', function () { consentString: '' }; - const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent); + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + expect(new URL(userSyncs[0].url).searchParams.get('gdpr_consent')).to.equal(''); + }); + + it('should return syncs array with one element of type iframe when gppConsent is empty string and applicableSections is empty', () => { + gppConsent = { + applicableSections: [], + gppString: '' + }; + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); expect(userSyncs).to.have.lengthOf(1); expect(userSyncs[0].type).to.equal('iframe'); - expect(userSyncs[0].url).to.equal('https://ms-cookie-sync.presage.io/user-sync.html?gdpr_consent=&source=prebid') + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with one element of type iframe when gppString is undefined', () => { + gppConsent = { + applicableSections: [7], + gppString: undefined + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + }); + + it('should return syncs array with one element of type iframe when gppString is null', () => { + gppConsent = { + applicableSections: [7], + gppString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal(gppConsent.applicableSections.toString()) + }); + + it('should return syncs array with one element of type iframe when gppConsent is undefined', () => { + gppConsent = undefined; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with one element of type iframe when gppConsent is null', () => { + gppConsent = null; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal('') + }); + + it('should return syncs array with one element of type iframe when gppConsent is null and applicableSections is empty', () => { + gppConsent = { + applicableSections: [], + gppString: null + }; + + const userSyncs = spec.getUserSyncs(syncOptions, [], gdprConsent, [], gppConsent); + expect(userSyncs).to.have.lengthOf(1); + expect(userSyncs[0].type).to.equal('iframe'); + + const urlParams = new URL(userSyncs[0].url).searchParams + expect(urlParams.get('gpp')).to.equal('') + expect(urlParams.get('gpp_sid')).to.equal('') }); }); }); @@ -394,6 +648,7 @@ describe('OguryBidAdapter', function () { }, ext: { ...bidRequests[0].params, + gpid: bidRequests[0].ortb2Imp.ext.gpid, timeSpentOnPage: stubbedCurrentTime } }, { @@ -412,7 +667,9 @@ describe('OguryBidAdapter', function () { }], regs: { ext: { - gdpr: 1 + gdpr: 1, + gpp: 'myGppString', + gpp_sid: [7] }, }, site: { @@ -441,7 +698,7 @@ describe('OguryBidAdapter', function () { }, ext: { prebidversion: '$prebid.version$', - adapterversion: '1.6.0' + adapterversion: '1.7.0' }, device: { w: stubbedWidth, @@ -661,7 +918,9 @@ describe('OguryBidAdapter', function () { ...expectedRequestObject, regs: { ext: { - gdpr: 0 + gdpr: 0, + gpp: 'myGppString', + gpp_sid: [7] }, }, user: { @@ -680,6 +939,33 @@ describe('OguryBidAdapter', function () { expect(request.data.regs.ext.gdpr).to.be.a('number'); }); + it('should not add gpp infos if not present', () => { + const bidderRequestWithoutGpp = { + ...bidderRequest, + gppConsent: {}, + } + const expectedRequestObjectWithoutGpp = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 1 + }, + }, + user: { + ext: { + consent: 'myConsentString', + uids: expectedRequestObject.user.ext.uids, + eids: expectedRequestObject.user.ext.eids + }, + } + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGpp); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGpp); + }); + it('should not add gdpr infos if gdprConsent is undefined', () => { const bidderRequestWithoutGdpr = { ...bidderRequest, @@ -689,7 +975,9 @@ describe('OguryBidAdapter', function () { ...expectedRequestObject, regs: { ext: { - gdpr: 0 + gdpr: 0, + gpp: 'myGppString', + gpp_sid: [7] }, }, user: { @@ -708,6 +996,26 @@ describe('OguryBidAdapter', function () { expect(request.data.regs.ext.gdpr).to.be.a('number'); }); + it('should not add gpp infos if gppConsent is undefined', () => { + const bidderRequestWithoutGdpr = { + ...bidderRequest, + gppConsent: undefined, + } + const expectedRequestObjectWithoutGdpr = { + ...expectedRequestObject, + regs: { + ext: { + gdpr: 1, + }, + }, + }; + + const validBidRequests = bidRequests + + const request = spec.buildRequests(validBidRequests, bidderRequestWithoutGdpr); + expect(request.data).to.deep.equal(expectedRequestObjectWithoutGdpr); + }); + it('should not add tcString and turn off gdpr-applies if consentString and gdprApplies are undefined', () => { const bidderRequestWithoutGdpr = { ...bidderRequest, @@ -717,7 +1025,9 @@ describe('OguryBidAdapter', function () { ...expectedRequestObject, regs: { ext: { - gdpr: 0 + gdpr: 0, + gpp: 'myGppString', + gpp_sid: [7] }, }, user: { @@ -824,7 +1134,47 @@ describe('OguryBidAdapter', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); expect(request.data).to.deep.equal(expectedRequestWithUnsupportedFloorCurrency); }); - }); + + it('should not add gpid if ortb2 undefined', () => { + const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) + + delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; + delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; + + const validBidRequests = utils.deepClone(bidRequests); + delete validBidRequests[0].ortb2Imp.ext.gpid; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + }); + + it('should not add gpid if gpid undefined', () => { + const expectedRequestWithUndefinedGpid = utils.deepClone(expectedRequestObject) + + delete expectedRequestWithUndefinedGpid.imp[0].ext.gpid; + delete expectedRequestWithUndefinedGpid.imp[1].ext.gpid; + + const validBidRequests = utils.deepClone(bidRequests); + validBidRequests[0] = { + ...validBidRequests[0], + ortb2Imp: { + ext: {} + } + }; + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestWithUndefinedGpid); + }); + + it('should send gpid in bid request', function() { + const validBidRequests = utils.deepClone(bidRequests) + + const request = spec.buildRequests(validBidRequests, bidderRequest); + expect(request.data).to.deep.equal(expectedRequestObject); + expect(request.data.imp[0].ext.gpid).to.be.a('string'); + expect(request.data.imp[1].ext.gpid).to.be.undefined + }) + }) describe('interpretResponse', function () { let openRtbBidResponse = { @@ -891,7 +1241,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[0].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[0].nurl, - adapterVersion: '1.6.0', + adapterVersion: '1.7.0', prebidVersion: '$prebid.version$' }, { requestId: openRtbBidResponse.body.seatbid[0].bid[1].impid, @@ -908,7 +1258,7 @@ describe('OguryBidAdapter', function () { advertiserDomains: openRtbBidResponse.body.seatbid[0].bid[1].adomain }, nurl: openRtbBidResponse.body.seatbid[0].bid[1].nurl, - adapterVersion: '1.6.0', + adapterVersion: '1.7.0', prebidVersion: '$prebid.version$' }] diff --git a/test/spec/modules/omsBidAdapter_spec.js b/test/spec/modules/omsBidAdapter_spec.js index 10a9c4c946c..dc2d8ffebb6 100644 --- a/test/spec/modules/omsBidAdapter_spec.js +++ b/test/spec/modules/omsBidAdapter_spec.js @@ -107,9 +107,9 @@ describe('omsBidAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -410,11 +410,67 @@ describe('omsBidAdapter', function () { }); describe('getUserSyncs ', () => { - let syncOptions = {iframeEnabled: true, pixelEnabled: true}; + const syncOptions = { iframeEnabled: true }; + const userSyncUrlIframe = 'https://rt.marphezis.com/sync?dpid=0'; + + it('returns empty syncs arr when syncOptions.iframeEnabled is false', () => { + expect(spec.getUserSyncs({ iframeEnabled: false }, {}, undefined, undefined)).to.be.empty; + }); + + it('returns syncs arr when syncOptions.iframeEnabled is true', () => { + expect(spec.getUserSyncs(syncOptions, {}, undefined, undefined)).to.deep.equal([{ + type: 'iframe', + url: userSyncUrlIframe + }]); + }); + + it('should pass gdpr param when gdprConsent.gdprApplies type is boolean', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: true }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=1` + }]); + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=0` + }]); + }); + + it('should pass gdpr_consent param when gdprConsent.consentString type is string', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false, consentString: 'test' }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}&gdpr=0&gdpr_consent=test` + }]); + }); + + it('should pass no params when gdprConsent.consentString and gdprConsent.gdprApplies types dont match', () => { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: 'true', consentString: 1 }, undefined)).to.deep.equal([{ + type: 'iframe', + url: `${userSyncUrlIframe}` + }]); + }); + + it('should pass us_privacy param when uspConsent is defined', function () { + expect(spec.getUserSyncs(syncOptions, {}, undefined, 'test')).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&us_privacy=test` + }]); + }); + + it('should pass gpp and gpp_sid params when gppConsent.gppString is defined', function () { + expect(spec.getUserSyncs(syncOptions, {}, {}, undefined, { + gppString: 'test', + applicableSections: [1, 2] + })).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&gpp=test&gpp_sid=1,2` + }]); + }); - it('should not return', () => { - let returnStatement = spec.getUserSyncs(syncOptions, []); - expect(returnStatement).to.be.empty; + it('should pass all params correctly', function () { + expect(spec.getUserSyncs(syncOptions, {}, { gdprApplies: false, consentString: 'test' }, 'test', { + gppString: 'test', + applicableSections: [] + })).to.deep.equal([{ + type: 'iframe', url: `${userSyncUrlIframe}&gdpr=0&gdpr_consent=test&us_privacy=test&gpp=test&gpp_sid=` + }]); }); }); }); diff --git a/test/spec/modules/onetagBidAdapter_spec.js b/test/spec/modules/onetagBidAdapter_spec.js index 93db5ffc57f..a6edaaabe79 100644 --- a/test/spec/modules/onetagBidAdapter_spec.js +++ b/test/spec/modules/onetagBidAdapter_spec.js @@ -226,6 +226,7 @@ describe('onetag', function () { 'bidId', 'bidderRequestId', 'pubId', + 'ortb2Imp', 'transactionId', 'context', 'playerSize', @@ -240,6 +241,7 @@ describe('onetag', function () { 'bidId', 'bidderRequestId', 'pubId', + 'ortb2Imp', 'transactionId', 'mediaTypeInfo', 'sizes', @@ -270,6 +272,7 @@ describe('onetag', function () { expect(payload.bids).to.exist.and.to.have.length(1); expect(payload.bids[0].auctionId).to.equal(bannerBid.ortb2.source.tid); expect(payload.bids[0].transactionId).to.equal(bannerBid.ortb2Imp.ext.tid); + expect(payload.bids[0].ortb2Imp).to.deep.equal(bannerBid.ortb2Imp); }); it('should send GDPR consent data', function () { let consentString = 'consentString'; @@ -433,13 +436,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': true + 'paapi': { + 'enabled': true + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag when FLEDGE is not enabled', function () { let bidderRequest = { @@ -447,13 +452,15 @@ describe('onetag', function () { 'auctionId': '1d1a030790a475', 'bidderRequestId': '22edbae2733bf6', 'timeout': 3000, - 'fledgeEnabled': false + paapi: { + enabled: false + } }; let serverRequest = spec.buildRequests([bannerBid], bidderRequest); const payload = JSON.parse(serverRequest.data); expect(payload.fledgeEnabled).to.exist; - expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.fledgeEnabled); + expect(payload.fledgeEnabled).to.exist.and.to.equal(bidderRequest.paapi.enabled); }); it('Should send FLEDGE eligibility flag set to false when fledgeEnabled is not defined', function () { let bidderRequest = { @@ -482,7 +489,7 @@ describe('onetag', function () { expect(fledgeInterpretedResponse.bids).to.satisfy(function (value) { return value === null || Array.isArray(value); }); - expect(fledgeInterpretedResponse.fledgeAuctionConfigs).to.be.an('array').that.is.not.empty; + expect(fledgeInterpretedResponse.paapi).to.be.an('array').that.is.not.empty; for (let i = 0; i < interpretedResponse.length; i++) { let dataItem = interpretedResponse[i]; expect(dataItem).to.include.all.keys('requestId', 'cpm', 'width', 'height', 'ttl', 'creativeId', 'netRevenue', 'currency', 'meta', 'dealId'); diff --git a/test/spec/modules/onomagicBidAdapter_spec.js b/test/spec/modules/onomagicBidAdapter_spec.js index 6ddc0edd477..c636542c9c9 100644 --- a/test/spec/modules/onomagicBidAdapter_spec.js +++ b/test/spec/modules/onomagicBidAdapter_spec.js @@ -92,9 +92,9 @@ describe('onomagicBidAdapter', function() { }); 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); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/openwebBidAdapter_spec.js b/test/spec/modules/openwebBidAdapter_spec.js index 4586ce78135..f6f6ad22476 100644 --- a/test/spec/modules/openwebBidAdapter_spec.js +++ b/test/spec/modules/openwebBidAdapter_spec.js @@ -25,7 +25,8 @@ describe('openwebAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [['640', '480']], 'params': { - 'org': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001', + 'placementId': '123' } }; @@ -33,7 +34,7 @@ describe('openwebAdapter', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); - it('should return false when required params are not found', function () { + it('should return false when org param is not found', function () { const newBid = Object.assign({}, bid); delete newBid.params; newBid.params = { @@ -41,6 +42,15 @@ describe('openwebAdapter', function () { }; expect(spec.isBidRequestValid(newBid)).to.equal(false); }); + + it('should return false when placementId param is not found', function () { + const newBid = Object.assign({}, bid); + delete newBid.params; + newBid.params = { + 'placementId': null + }; + expect(spec.isBidRequestValid(newBid)).to.equal(false); + }); }); describe('buildRequests', function () { @@ -50,7 +60,8 @@ describe('openwebAdapter', function () { 'adUnitCode': 'adunit-code', 'sizes': [[640, 480]], 'params': { - 'org': 'jdye8weeyirk00000001' + 'org': 'jdye8weeyirk00000001', + 'placementId': '123' }, 'bidId': '299ffc8cca0b87', 'loop': 1, @@ -103,15 +114,13 @@ describe('openwebAdapter', function () { const bidderRequest = { bidderCode: 'openweb', } - const placementId = '12345678'; const api = [1, 2]; const mimes = ['application/javascript', 'video/mp4', 'video/quicktime']; const protocols = [2, 3, 5, 6]; it('sends the placementId to ENDPOINT via POST', function () { - bidRequests[0].params.placementId = placementId; const request = spec.buildRequests(bidRequests, bidderRequest); - expect(request.data.bids[0].placementId).to.equal(placementId); + expect(request.data.bids[0].placementId).to.equal('123'); }); it('sends the plcmt to ENDPOINT via POST', function () { @@ -440,6 +449,8 @@ describe('openwebAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id-1', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: VIDEO }, @@ -449,6 +460,8 @@ describe('openwebAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id-2', + nurl: 'http://example.com/win/1234', adomain: ['abc.com'], mediaType: BANNER }] @@ -461,7 +474,7 @@ describe('openwebAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-1', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +489,10 @@ describe('openwebAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-2', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +505,8 @@ describe('openwebAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { diff --git a/test/spec/modules/openxBidAdapter_spec.js b/test/spec/modules/openxBidAdapter_spec.js index 7c504bca50b..5dc60b25ab0 100644 --- a/test/spec/modules/openxBidAdapter_spec.js +++ b/test/spec/modules/openxBidAdapter_spec.js @@ -10,14 +10,15 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {version} from 'package.json'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; - const DEFAULT_SYNC = SYNC_URL + '?ph=' + DEFAULT_PH; const BidRequestBuilder = function BidRequestBuilder(options) { @@ -187,9 +188,9 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses both delDomain and platform', () => { @@ -216,9 +217,9 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithDelDomainAndPlatform); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses mediaType', () => { @@ -241,10 +242,10 @@ describe('OpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + let invalidVideoBidWithMediaType = Object.assign({}, videoBidWithMediaType); + delete invalidVideoBidWithMediaType.params; + invalidVideoBidWithMediaType.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaType)).to.equal(false); }); }); }); @@ -971,7 +972,7 @@ describe('OpenxRtbAdapter', function () { }); context('when there are userid providers', function () { - const userIdAsEids = [ + const eids = [ { source: 'adserver.org', uids: [{ @@ -1002,14 +1003,12 @@ describe('OpenxRtbAdapter', function () { ]; it(`should send the user id under the extended ids`, function () { - const bidRequestsWithUserId = [{ + const bidRequests = [{ bidder: 'openx', params: { unit: '11', delDomain: 'test-del-domain' }, - userId: { - }, adUnitCode: 'adunit-code', mediaTypes: { banner: { @@ -1019,12 +1018,12 @@ describe('OpenxRtbAdapter', function () { bidId: 'test-bid-id-1', bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1', - userIdAsEids: userIdAsEids }]; // enrich bid request with userId key/value - const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + mockBidderRequest.ortb2 = {user: {ext: {eids}}} + const request = spec.buildRequests(bidRequests, mockBidderRequest); + expect(request[0].data.user.ext.eids).to.eql(eids); }); it(`when no user ids are available, it should not send any extended ids`, function () { @@ -1037,7 +1036,9 @@ describe('OpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); @@ -1087,7 +1088,6 @@ describe('OpenxRtbAdapter', function () { skipafter: 4, minduration: 10, maxduration: 30, - placement: 4, protocols: [8], w: 300, h: 250 @@ -1107,7 +1107,7 @@ describe('OpenxRtbAdapter', function () { let bid; context('when there is an nbr response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1129,16 +1129,16 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = {nbr: 0}; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); context('when no seatbid in response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1160,16 +1160,16 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = {ext: {}, id: 'test-bid-id'}; - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); context('when there is no response', function () { - let bids; + let response; beforeEach(function () { bidRequestConfigs = [{ bidder: 'openx', @@ -1191,11 +1191,11 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = ''; // Unknown error - bids = spec.interpretResponse({body: bidResponse}, bidRequest); + response = spec.interpretResponse({body: bidResponse}, bidRequest); }); it('should not return any bids', function () { - expect(bids.length).to.equal(0); + expect(response.bids.length).to.equal(0); }); }); @@ -1230,19 +1230,11 @@ describe('OpenxRtbAdapter', function () { ext: { dsp_id: '123', buyer_id: '456', - brand_id: '789', - paf: { - content_id: 'paf_content_id' - } + brand_id: '789' } }] }], - cur: 'AUS', - ext: { - paf: { - transmission: {version: '12'} - } - } + cur: 'AUS' }; context('when there is a response, the common response properties', function () { @@ -1251,7 +1243,7 @@ describe('OpenxRtbAdapter', function () { bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; bidResponse = deepClone(SAMPLE_BID_RESPONSE); - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; }); it('should return a price', function () { @@ -1306,33 +1298,8 @@ describe('OpenxRtbAdapter', function () { it('should return adomain', function () { expect(bid.meta.advertiserDomains).to.equal(bidResponse.seatbid[0].bid[0].adomain); }); - - it('should return paf fields', function () { - const paf = { - transmission: {version: '12'}, - content_id: 'paf_content_id' - } - expect(bid.meta.paf).to.deep.equal(paf); - }); }); - context('when there is more than one response', () => { - let bids; - beforeEach(function () { - bidRequestConfigs = deepClone(SAMPLE_BID_REQUESTS); - bidRequest = spec.buildRequests(bidRequestConfigs, {refererInfo: {}})[0]; - bidResponse = deepClone(SAMPLE_BID_RESPONSE); - bidResponse.seatbid[0].bid.push(deepClone(bidResponse.seatbid[0].bid[0])); - bidResponse.seatbid[0].bid[1].ext.paf.content_id = 'second_paf' - - bids = spec.interpretResponse({body: bidResponse}, bidRequest); - }); - - it('should not confuse paf content_id', () => { - expect(bids.map(b => b.meta.paf.content_id)).to.eql(['paf_content_id', 'second_paf']); - }); - }) - context('when the response is a banner', function() { beforeEach(function () { bidRequestConfigs = [{ @@ -1369,7 +1336,7 @@ describe('OpenxRtbAdapter', function () { cur: 'AUS' }; - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; }); it('should return the proper mediaType', function () { @@ -1418,14 +1385,14 @@ describe('OpenxRtbAdapter', function () { }); it('should return the proper mediaType', function () { - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; expect(bid.mediaType).to.equal(Object.keys(bidRequestConfigs[0].mediaTypes)[0]); }); it('should return the proper mediaType', function () { const winUrl = 'https//my.win.url'; bidResponse.seatbid[0].bid[0].nurl = winUrl - bid = spec.interpretResponse({body: bidResponse}, bidRequest)[0]; + bid = spec.interpretResponse({body: bidResponse}, bidRequest).bids[0]; expect(bid.vastUrl).to.equal(winUrl); }); @@ -1504,13 +1471,13 @@ describe('OpenxRtbAdapter', function () { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); }); it('should inject ortb2Imp in auctionSignals', function () { - const auctionConfig = response.fledgeAuctionConfigs[0].config; + const auctionConfig = response.paapi[0].config; expect(auctionConfig).to.deep.include({ auctionSignals: { ortb2Imp: { @@ -1522,7 +1489,8 @@ describe('OpenxRtbAdapter', function () { }, ext: { divid: 'adunit-code', - } + }, + secure: 1 } } }); diff --git a/test/spec/modules/operaadsIdSystem_spec.js b/test/spec/modules/operaadsIdSystem_spec.js index d81f643d62f..b6acb942331 100644 --- a/test/spec/modules/operaadsIdSystem_spec.js +++ b/test/spec/modules/operaadsIdSystem_spec.js @@ -1,53 +1,76 @@ import { operaIdSubmodule } from 'modules/operaadsIdSystem' import * as ajaxLib from 'src/ajax.js' +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const TEST_ID = 'opera-test-id'; const operaIdRemoteResponse = { uid: TEST_ID }; - -describe('operaId submodule properties', () => { - it('should expose a "name" property equal to "operaId"', () => { - expect(operaIdSubmodule.name).to.equal('operaId'); +describe('operaads ID', () => { + describe('operaId submodule properties', () => { + it('should expose a "name" property equal to "operaId"', () => { + expect(operaIdSubmodule.name).to.equal('operaId'); + }); }); -}); -function fakeRequest(fn) { - const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { - return (url, cbObj) => { - cbObj.success(JSON.stringify(operaIdRemoteResponse)); - } - }); - fn(); - ajaxBuilderStub.restore(); -} + function fakeRequest(fn) { + const ajaxBuilderStub = sinon.stub(ajaxLib, 'ajaxBuilder').callsFake(() => { + return (url, cbObj) => { + cbObj.success(JSON.stringify(operaIdRemoteResponse)); + } + }); + fn(); + ajaxBuilderStub.restore(); + } -describe('operaId submodule getId', function() { - it('request to the fake server to correctly extract test ID', function() { - fakeRequest(() => { - const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: { pid: 'pub123' } }); - moduleIdCallbackResponse.callback((id) => { - expect(id).to.equal(operaIdRemoteResponse.operaId); + describe('operaId submodule getId', function() { + it('request to the fake server to correctly extract test ID', function() { + fakeRequest(() => { + const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: { pid: 'pub123' } }); + moduleIdCallbackResponse.callback((id) => { + expect(id).to.equal(operaIdRemoteResponse.operaId); + }); }); }); - }); - it('request to the fake server without publiser ID', function() { - fakeRequest(() => { - const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: {} }); - expect(moduleIdCallbackResponse).to.equal(undefined); + it('request to the fake server without publiser ID', function() { + fakeRequest(() => { + const moduleIdCallbackResponse = operaIdSubmodule.getId({ params: {} }); + expect(moduleIdCallbackResponse).to.equal(undefined); + }); }); }); -}); -describe('operaId submodule decode', function() { - it('should respond with an object containing "operaId" as key with the value', () => { - expect(operaIdSubmodule.decode(TEST_ID)).to.deep.equal({ - operaId: TEST_ID + describe('operaId submodule decode', function() { + it('should respond with an object containing "operaId" as key with the value', () => { + expect(operaIdSubmodule.decode(TEST_ID)).to.deep.equal({ + operaId: TEST_ID + }); }); - }); - it('should respond with undefined if the value is not a string or an empty string', () => { - [1, 2.0, null, undefined, NaN, [], {}].forEach((value) => { - expect(operaIdSubmodule.decode(value)).to.equal(undefined); + it('should respond with undefined if the value is not a string or an empty string', () => { + [1, 2.0, null, undefined, NaN, [], {}].forEach((value) => { + expect(operaIdSubmodule.decode(value)).to.equal(undefined); + }); }); }); -}); + describe('eid', () => { + before(() => { + attachIdSystem(operaIdSubmodule); + }); + it('operaId', function() { + const userId = { + operaId: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 't.adx.opera.com', + uids: [{ + id: 'some-random-id-value', + atype: 1 + }] + }); + }); + }) +}) diff --git a/test/spec/modules/optableBidAdapter_spec.js b/test/spec/modules/optableBidAdapter_spec.js index d7f2230328e..b7cf2e3b44d 100644 --- a/test/spec/modules/optableBidAdapter_spec.js +++ b/test/spec/modules/optableBidAdapter_spec.js @@ -47,6 +47,33 @@ describe('optableBidAdapter', function() { }); }); + describe('buildPAAPIConfigs', () => { + function makeRequest({bidId, site = 'mockSite', ae = 1}) { + return { + bidId, + params: { + site + }, + ortb2Imp: { + ext: {ae} + } + } + } + it('should generate auction configs for ae requests', () => { + const configs = spec.buildPAAPIConfigs([ + makeRequest({bidId: 'bid1', ae: 1}), + makeRequest({bidId: 'bid2', ae: 0}), + makeRequest({bidId: 'bid3', ae: 1}), + ]); + expect(configs.map(cfg => cfg.bidId)).to.eql(['bid1', 'bid3']); + configs.forEach(cfg => sinon.assert.match(cfg.config, { + seller: 'https://ads.optable.co', + decisionLogicURL: `https://ads.optable.co/ca/paapi/v1/ssp/decision-logic.js?origin=mockSite`, + interestGroupBuyers: ['https://ads.optable.co'] + })) + }) + }) + describe('interpretResponse', function() { const validBid = { bidder: 'optable', @@ -78,10 +105,10 @@ describe('optableBidAdapter', function() { } }; - it('maps fledgeAuctionConfigs from ext.optable.fledge.auctionconfigs', function() { + it('maps paapi from ext.optable.fledge.auctionconfigs', function() { const request = spec.buildRequests([validBid], bidderRequest); const result = spec.interpretResponse(response, request); - expect(result.fledgeAuctionConfigs).to.deep.equal([ + expect(result.paapi).to.deep.equal([ { bidId: 'bid123', config: { seller: 'https://ads.optable.co' } } ]); }); diff --git a/test/spec/modules/optimeraRtdProvider_spec.js b/test/spec/modules/optimeraRtdProvider_spec.js index 8a9f000bbb9..e1d962d306c 100644 --- a/test/spec/modules/optimeraRtdProvider_spec.js +++ b/test/spec/modules/optimeraRtdProvider_spec.js @@ -37,7 +37,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { optimeraRTD.init(conf.dataProviders[0]); optimeraRTD.setScores(); expect(optimeraRTD.apiVersion).to.equal('v0'); - expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); }); it('should properly set the score file URL without apiVersion set', () => { @@ -54,7 +54,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { optimeraRTD.init(conf.dataProviders[0]); optimeraRTD.setScores(); expect(optimeraRTD.apiVersion).to.equal('v0'); - expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); }); it('should properly set the score file URL with an api version other than v0 or v1', () => { @@ -71,7 +71,7 @@ describe('Optimera RTD score file URL is properly set for v0', () => { }; optimeraRTD.init(conf.dataProviders[0]); optimeraRTD.setScores(); - expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost:9876/context.html.js'); + expect(optimeraRTD.scoresURL).to.equal('https://dyv1bugovvq1g.cloudfront.net/9999/localhost%3A9876/context.html.js'); }); }); diff --git a/test/spec/modules/orakiBidAdapter_spec.js b/test/spec/modules/orakiBidAdapter_spec.js new file mode 100644 index 00000000000..9a7c777212b --- /dev/null +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -0,0 +1,511 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/orakiBidAdapter'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes'; +import { getUniqueIdentifierStr } from '../../../src/utils'; + +const bidder = 'oraki'; + +describe('OrakiBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK', + } + }, + timeout: 500 + }; + + 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 general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'deviceWidth', + 'deviceHeight', + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://sync.oraki.io/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://sync.oraki.io/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://sync.oraki.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/outbrainBidAdapter_spec.js b/test/spec/modules/outbrainBidAdapter_spec.js index e6abb5e9caa..5e9d76b59b9 100644 --- a/test/spec/modules/outbrainBidAdapter_spec.js +++ b/test/spec/modules/outbrainBidAdapter_spec.js @@ -58,12 +58,30 @@ describe('Outbrain Adapter', function () { minduration: 3, maxduration: 10, startdelay: 2, - placement: 4, + plcmt: 4, + placement: 5, linearity: 1 } } } + const ortb2WithDeviceData = { + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4' + } + } + }; + describe('isBidRequestValid', function () { before(() => { config.setConfig({ @@ -389,7 +407,8 @@ describe('Outbrain Adapter', function () { minduration: 3, maxduration: 10, startdelay: 2, - placement: 4, + placement: 5, + plcmt: 4, linearity: 1 } } @@ -619,6 +638,19 @@ describe('Outbrain Adapter', function () { const resData = JSON.parse(res.data) expect(resData.imp[0].native.request).to.equal(JSON.stringify(expectedNativeAssets)); }); + + it('should pass ortb2 device data', function () { + const bidRequest = { + ...commonBidRequest, + ...nativeBidRequestParams, + }; + + const res = spec.buildRequests( + [bidRequest], + {...commonBidderRequest, ...ortb2WithDeviceData}, + ); + expect(JSON.parse(res.data).device).to.deep.equal(ortb2WithDeviceData.ortb2.device); + }); }) describe('interpretResponse', function () { diff --git a/test/spec/modules/ownadxBidAdapter_spec.js b/test/spec/modules/ownadxBidAdapter_spec.js new file mode 100644 index 00000000000..13d69b3a261 --- /dev/null +++ b/test/spec/modules/ownadxBidAdapter_spec.js @@ -0,0 +1,103 @@ +import { expect } from 'chai'; +import { spec } from 'modules/ownadxBidAdapter.js'; + +describe('ownadx', function () { + const METHOD = 'POST'; + const URL = 'https://pbs-js.prebid-ownadx.com/publisher/prebid/9/1231?token=3f2941af4f7e446f9a19ca6045f8cff4'; + + const bidRequest = { + bidder: 'ownadx', + params: { + tokenId: '3f2941af4f7e446f9a19ca6045f8cff4', + sspId: '1231', + seatId: '9' + }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 600]] + } + }, + sizes: [ + [300, 250], + [300, 600] + ], + bidId: 'bid-id-123456', + adUnitCode: 'ad-unit-code-1', + bidderRequestId: 'bidder-request-id-123456', + auctionId: 'auction-id-123456', + transactionId: 'transaction-id-123456' + }; + + describe('isBidRequestValid', function () { + it('should return true where required params found', function () { + expect(spec.isBidRequestValid(bidRequest)).to.equal(true); + }); + }); + + describe('buildRequests', function () { + let bidderRequest = { + refererInfo: { + page: 'https://www.test.com', + reachedTop: true, + isAmp: false, + numIframes: 0, + stack: [ + 'https://www.test.com' + ], + canonicalUrl: null + } + }; + + it('should build correct POST request for banner bid', function () { + const request = spec.buildRequests([bidRequest], bidderRequest)[0]; + expect(request).to.be.an('object'); + expect(request.method).to.equal('POST'); + expect(request.url).to.equal('https://pbs-js.prebid-ownadx.com/publisher/prebid/9/1231?token=3f2941af4f7e446f9a19ca6045f8cff4'); + const payload = request.data; + expect(payload).to.be.an('object'); + expect(payload.sizes).to.be.an('array'); + expect(payload.slotBidId).to.be.a('string'); + expect(payload.PageUrl).to.be.a('string'); + expect(payload.mediaChannel).to.be.a('number'); + }); + }); + + describe('interpretResponse', function () { + let serverResponse = { + body: { + tokenId: '3f2941af4f7e446f9a19ca6045f8cff4', + bid: 'BID-XXXX-XXXX', + width: '300', + height: '250', + cpm: '0.7', + adm: '

Ad from OwnAdX

', + slotBidId: 'bid-id-123456', + adType: '1', + statusText: 'Success' + } + }; + + let expectedResponse = [{ + token: '3f2941af4f7e446f9a19ca6045f8cff4', + requestId: 'bid-id-123456', + cpm: '0.7', + currency: 'USD', + aType: '1', + netRevenue: false, + width: '300', + height: '250', + creativeId: 0, + ttl: 300, + ad: '

Ad from OwnAdX

', + meta: { + mediaType: 'banner', + advertiserDomains: [] + } + }]; + + it('should correctly interpret valid banner response', function () { + let result = spec.interpretResponse(serverResponse); + expect(result).to.deep.equal(expectedResponse); + }); + }); +}); diff --git a/test/spec/modules/ozoneBidAdapter_spec.js b/test/spec/modules/ozoneBidAdapter_spec.js index 73df2fba8fd..f504af91df5 100644 --- a/test/spec/modules/ozoneBidAdapter_spec.js +++ b/test/spec/modules/ozoneBidAdapter_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import {Renderer} from '../../../src/Renderer.js'; import {getGranularityKeyName, getGranularityObject} from '../../../modules/ozoneBidAdapter.js'; import * as utils from '../../../src/utils.js'; +import {deepSetValue} from '../../../src/utils.js'; const OZONEURI = 'https://elb.the-ozone-project.com/openrtb2/auction'; const BIDDER_CODE = 'ozone'; var validBidRequests = [ @@ -62,6 +63,699 @@ var validBidRequestsMulti = [ transactionId: '2e63c0ed-b10c-4008-aed5-84582cecfe87' } ]; +var validBidRequestsWithAuctionIdTransactionId = [{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dda', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}]; +var valid6BidRequestsWithAuctionIdTransactionId = [{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dda', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu2', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddb', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu3', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddc', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu4', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddd', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu5', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1dde', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}, +{ + 'bidder': 'ozone', + 'params': { + 'publisherId': 'OZONENUK0001', + 'siteId': '4204204201', + 'placementId': '8000000330', + 'customData': [ + { + 'settings': {}, + 'targeting': { + 'sens': 'f', + 'pt1': '/uk', + 'pt5': [ + 'uk' + ], + 'pt7': 'desktop', + 'pt9': '|k0xw2vqzp33kklb3j5w4|||' + } + } + ] + }, + 'ortb2Imp': { + 'ext': { + 'gpid': 'mpu_pbadslot_from_adunit', + 'data': { + 'pbadslot': 'mpu_pbadslot_from_adunit', + 'adserver': { + 'name': 'gam', + 'adslot': '/22037345/projectozone' + } + }, + 'tid': 'f0dac8b5-09df-4da7-9d83-c99786d4517a' + } + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'mpu6', + 'transactionId': 'f0dac8b5-09df-4da7-9d83-c99786d4517a', + 'adUnitId': '715b4bdc-515f-488b-8633-333654e72f3f', + 'sizes': [ + [ + 300, + 250 + ], + [ + 300, + 600 + ] + ], + 'bidId': '3da18cc31f1ddf', + 'bidderRequestId': '263c3b0d970326', + 'auctionId': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ortb2': { + 'source': { + 'tid': 'a9c479d0-d9cc-4505-a0a6-5982ce8fb8f0' + }, + 'regs': { + 'ext': { + 'gdpr': 1, + 'us_privacy': '1Y--' + } + }, + 'user': { + 'ext': { + 'consent': 'CQAaAwAQAaAwAAKA1AENA5EsAP_gAEPgACiQKRNV_G__bWlr8X73aftkeY1P9_h77sQxBhfJE-4FzLuW_JwXx2ExNA36tqIKmRIEu3bBIQNlHJDUTVCgaogVryDMakWcoTNKJ6BkiFMRO2dYCF5vmwtj-QKY5vr993dx2B-t_dv83dzyz4VHn3a5_2e0WJCdA58tDfv9bROb-9IPd_58v4v8_F_rE2_eT1l_tevp7D9-cts7_XW-9_fff79Ll_-mBwUcALMNCogDLIkJCDQMIIEAKgrCAigQAAAAkDRAQAmDAp2BgEusJEAIAUAAwQAgABRkACAAASABCIAIACgQAAQCBQAAgAACAQAMDAAGACwEAgABAdAhTAggUCwASMyIhTAgCgSCAlsqEEgCBBXCEIs8CCAREwUAAAJABWAAICwWAxJICViQQJcQbQAAEACAQQAVCKTswBBAGbLVXiibRlaQFo-ACjgAAAAA.YAAAAAAAAAAA' + } + }, + 'site': { + 'domain': 'ardm.io', + 'publisher': { + 'domain': 'ardm.io' + }, + 'page': 'https://www.ardm.io/ozone/2.9.4/20240715-test-singlereq-optin.html?pbjs_debug=true' + }, + 'device': { + 'w': 1609, + 'h': 279, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36', + 'language': 'en' + } + } +}]; var validBidRequestsWithUserIdData = [ { adUnitCode: 'div-gpt-ad-1460505748561-0', @@ -401,6 +1095,66 @@ var validBidderRequest = { start: 1536838908987, timeout: 3000 }; +var validBidderRequestWithCookieDeprecation = { + auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', + auctionStart: 1536838908986, + bidderCode: 'ozone', + bidderRequestId: '1c1586b27a1b5c8', + bids: [{ + 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', 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' + }], + doneCbCallCount: 1, + start: 1536838908987, + timeout: 3000, + ortb2: { + 'device': { + 'w': 1617, + 'h': 317, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36', + 'language': 'en', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Google Chrome', + 'version': [ + '125' + ] + }, + { + 'brand': 'Chromium', + 'version': [ + '125' + ] + }, + { + 'brand': 'Not.A/Brand', + 'version': [ + '24' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'fake_control_2' + } + } + } +}; var bidderRequestWithFullGdpr = { auctionId: '27dcb421-95c6-4024-a624-3c03816c5f99', auctionStart: 1536838908986, @@ -1814,7 +2568,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when ozone is true', 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; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1832,7 +2586,7 @@ describe('ozone Adapter', function () { }); it('should add gdpr consent information to the request when vendorData is missing vendorConsents (Mirror)', function () { let consentString = 'BOcocyaOcocyaAfEYDENCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NphLgA=='; - let bidderRequest = validBidderRequest; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: true, @@ -1848,7 +2602,7 @@ describe('ozone Adapter', function () { }); 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; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1863,9 +2617,24 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); expect(payload.regs.ext.gdpr).to.equal(0); }); + it('should set gpp and gpp_sid when available', function() { + let gppString = 'gppConsentString'; + let gppSections = [7, 8, 9]; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + bidderRequest.ortb2 = {regs: {gpp: gppString, gpp_sid: gppSections}}; + const request = spec.buildRequests(validBidRequestsNoSizes, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.regs.gpp).to.equal(gppString); + expect(payload.regs.gpp_sid).to.have.same.members(gppSections); + }); + it('should not set gpp and gpp_sid keys when not available', function() { + const request = spec.buildRequests(validBidRequestsNoSizes, validBidderRequest); + const payload = JSON.parse(request.data); + expect(payload).to.not.contain.keys(['gpp', 'gpp_sid', 'ext', 'regs']); + }); it('should not have imp[N].ext.ozone.userId', 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; + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); bidderRequest.gdprConsent = { consentString: consentString, gdprApplies: false, @@ -1876,7 +2645,7 @@ describe('ozone Adapter', function () { purposeConsents: {1: true, 2: true, 3: true, 4: true, 5: true} } }; - let bidRequests = validBidRequests; + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); bidRequests[0]['userId'] = { 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, @@ -1891,23 +2660,12 @@ describe('ozone Adapter', function () { const payload = JSON.parse(request.data); let firstBid = payload.imp[0].ext.ozone; expect(firstBid).to.not.have.property('userId'); - delete validBidRequests[0].userId; // tidy up now, else it will screw with other tests }); it('should pick up the value of pubcid when built using the pubCommonId module (not userId)', function () { let bidRequests = validBidRequests; - bidRequests[0]['userId'] = { - 'digitrustid': {data: {id: 'DTID', keyv: 4, privacy: {optout: false}, producer: 'ABC', version: 2}}, - 'id5id': { uid: '1111', ext: { linkType: 2, abTestingControlGroup: false } }, - 'idl_env': '3333', - 'parrableid': 'eidVersion.encryptionKeyReference.encryptedValue', - 'tdid': '6666', - 'sharedid': {'id': '01EAJWWNEPN3CYMM5N8M5VXY22', 'third': '01EAJWWNEPN3CYMM5N8M5VXY22'} - }; - bidRequests[0]['userIdAsEids'] = validBidRequestsWithUserIdData[0]['userIdAsEids']; const request = spec.buildRequests(bidRequests, validBidderRequest); 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) Updated Aug 2020', function() { const request = spec.buildRequests(validBidRequestsWithUserIdData, validBidderRequest); @@ -2056,7 +2814,7 @@ describe('ozone Adapter', function () { const data = JSON.parse(request.data); expect(data.imp[0].ext.gpid).to.equal('/22037345/projectozone'); }); - it('should batch into 10s if config is set', function () { + it('should batch into 10s if config is set to true', function () { config.setConfig({ozone: {'batchRequests': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2069,7 +2827,20 @@ describe('ozone Adapter', function () { expect(request.length).to.equal(3); config.resetConfig(); }); - it('should not batch into 10s if config is set to false and singleRequest is true', function () { + it('should batch into 7 if config is set to 7', function () { + config.setConfig({ozone: {'batchRequests': 7}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(4); + config.resetConfig(); + }); + it('should not batch if config is set to false and singleRequest is true', function () { config.setConfig({ozone: {'batchRequests': false, 'singleRequest': true}}); var specMock = utils.deepClone(spec); let arrReq = []; @@ -2082,6 +2853,57 @@ describe('ozone Adapter', function () { expect(request.method).to.equal('POST'); config.resetConfig(); }); + it('should not batch if config is set to invalid value -10 and singleRequest is true', function () { + config.setConfig({ozone: {'batchRequests': -10, 'singleRequest': true}}); + var specMock = utils.deepClone(spec); + let arrReq = []; + for (let i = 0; i < 15; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + const request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); + config.resetConfig(); + }); + it('should use GET values for batchRequests if found', function() { + var specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '5'}; + }; + let arrReq = []; + for (let i = 0; i < 25; i++) { + let b = validBidRequests[0]; + b.adUnitCode += i; + arrReq.push(b); + } + let request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(5); // 5 x 5 = 25 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': '10'}; // the built in function will return '10' (string) + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.length).to.equal(3); // 10, 10, 5 + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': true}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': 'true'}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching - GET param must be numeric + specMock = utils.deepClone(spec); + specMock.getGetParametersAsObject = function() { + return {'batchRequests': -5}; + }; + request = specMock.buildRequests(arrReq, validBidderRequest); + expect(request.method).to.equal('POST'); // no batching + }); it('should use GET values auction=dev & cookiesync=dev if set', function() { var specMock = utils.deepClone(spec); specMock.getGetParametersAsObject = function() { @@ -2295,6 +3117,67 @@ describe('ozone Adapter', function () { expect(data.source.ext).to.haveOwnProperty('schain'); expect(data.source.ext.schain).to.deep.equal(schainConfigObject); // .deep.equal() : Target object deeply (but not strictly) equals `{a: 1}` }); + it('should find ortb2 cookieDeprecation values', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequestWithCookieDeprecation)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('fake_control_2'); + }); + it('should set ortb2 cookieDeprecation to "none" if there is none', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + const request = spec.buildRequests(validBidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.ext.ozone.cookieDeprecationLabel).to.equal('none'); + }); + it('should handle fledge requests', function () { + let bidderRequest = JSON.parse(JSON.stringify(validBidderRequest)); + let bidRequests = JSON.parse(JSON.stringify(validBidRequests)); + deepSetValue(bidRequests[0], 'ortb2Imp.ext.ae', 1); + bidderRequest.fledgeEnabled = true; + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + expect(payload.imp[0].ext.ae).to.equal(1); + }); + it('Single request: should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'singleRequest': true}}); + specMock.loadWhitelabelData(validBidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(validBidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Object'); + const payload = JSON.parse(request.data); + expect(payload.source.tid).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); + it('non-Single request: should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'singleRequest': false}}); + specMock.loadWhitelabelData(validBidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(validBidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Array'); + const payload = JSON.parse(request[0].data); + expect(payload.source.tid).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(validBidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); + it('Batch request (flat array of single requests): should use ortb auction ID & transaction ID values if set (this will be the case when publisher opts in with config)', function() { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel = null; + config.setConfig({'ozone': {'batchRequests': 3}}); + specMock.loadWhitelabelData(valid6BidRequestsWithAuctionIdTransactionId[0]); + const request = specMock.buildRequests(valid6BidRequestsWithAuctionIdTransactionId, validBidderRequest); // I don't look in the bidderRequest for this - there's no point + expect(request).to.be.an('Array'); + expect(request).to.have.lengthOf(2); + const payload = JSON.parse(request[0].data); + expect(payload.source.tid).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.auctionId).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2.source.tid); + expect(payload.imp[0].ext.ozone.transactionId).to.equal(valid6BidRequestsWithAuctionIdTransactionId[0].ortb2Imp.ext.tid); + config.resetConfig(); + }); }); describe('interpretResponse', function () { beforeEach(function () { @@ -2509,6 +3392,20 @@ describe('ozone Adapter', function () { const bid = result[0]; expect(bid.mediaType).to.equal('video'); }); + it('should handle fledge response', function () { + const req = spec.buildRequests(validBidRequests, validBidderRequest); + let objResp = JSON.parse(JSON.stringify(validResponse)); + objResp.body.ext = {igi: [{ + 'impid': '1', + 'igb': [{ + 'origin': 'https://paapi.dsp.com', + 'pbs': '{"key": "value"}' + }] + }]}; + const result = spec.interpretResponse(objResp, req); + expect(result).to.be.an('object'); + expect(result.fledgeAuctionConfigs[0]['impid']).to.equal('1'); + }); }); describe('userSyncs', function () { it('should fail gracefully if no server response', function () { @@ -2540,6 +3437,10 @@ describe('ozone Adapter', function () { expect(result).to.be.an('array'); expect(result[0].url).to.include('usp_consent=&'); }); + it('should add gpp if its present', function () { + const result = spec.getUserSyncs({iframeEnabled: true}, 'good server response', gdpr1, '1---', { gppString: 'gppStringHere', applicableSections: [7, 8, 9] }); + expect(result[0].url).to.include('gpp=gppStringHere&gpp_sid=7,8,9'); + }); }); describe('video object utils', function () { it('should find width & height from video object', function () { @@ -2698,7 +3599,7 @@ describe('ozone Adapter', function () { }); }); describe('addVideoDefaults', function() { - it('should correctly add video defaults', function () { + it('should not add video defaults if there is no videoParams config', function () { let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2715,12 +3616,14 @@ describe('ozone Adapter', function () { testKey: 'child value' }; let result = spec.addVideoDefaults({}, mediaTypes, mediaTypes); - expect(result.placement).to.equal(3); + expect(result.placement).to.be.undefined; expect(result.skip).to.equal(0); result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.skip).to.equal(1); }); - it('should correctly add video defaults including skippable in parent', function () { + it('should correctly add video defaults if page config videoParams is defined, also check skip in the parent', function () { + var specMock = utils.deepClone(spec); + specMock.propertyBag.whitelabel.videoParams = {outstream: 3, instream: 1}; let mediaTypes = { playerSize: [640, 480], mimes: ['video/mp4'], @@ -2736,7 +3639,7 @@ describe('ozone Adapter', function () { skipafter: 5, testKey: 'child value' }; - let result = spec.addVideoDefaults({}, mediaTypes, bid_params_video); + let result = specMock.addVideoDefaults({}, mediaTypes, bid_params_video); expect(result.placement).to.equal(3); expect(result.skip).to.equal(1); }); @@ -2772,6 +3675,7 @@ describe('ozone Adapter', function () { let testKey2 = 'ozone.singleRequest'; let markbidder_config2 = specMock.getWhitelabelConfigItem(testKey2); expect(markbidder_config2).to.equal('markbidder-singlerequest-value'); + config.resetConfig(); }); }); describe('setBidMediaTypeIfNotExist', function() { diff --git a/test/spec/modules/paapiForGpt_spec.js b/test/spec/modules/paapiForGpt_spec.js new file mode 100644 index 00000000000..9a6637f82aa --- /dev/null +++ b/test/spec/modules/paapiForGpt_spec.js @@ -0,0 +1,216 @@ +import { + getPAAPISizeHook, + onAuctionConfigFactory, + setPAAPIConfigFactory, setTargetingHookFactory, + slotConfigurator +} from 'modules/paapiForGpt.js'; +import * as gptUtils from '../../../libraries/gptUtils/gptUtils.js'; +import 'modules/appnexusBidAdapter.js'; +import 'modules/rubiconBidAdapter.js'; +import {deepSetValue} from '../../../src/utils.js'; +import {config} from 'src/config.js'; + +describe('paapiForGpt module', () => { + let sandbox, fledgeAuctionConfig; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + fledgeAuctionConfig = { + seller: 'bidder', + mock: 'config' + }; + }); + afterEach(() => { + sandbox.restore(); + }); + + describe('slotConfigurator', () => { + let setGptConfig; + function mockGptSlot(auPath) { + return { + setConfig: sinon.stub(), + getAdUnitPath: () => auPath + } + } + beforeEach(() => { + setGptConfig = slotConfigurator(); + }); + + Object.entries({ + 'single slot': [mockGptSlot('mock/gpt/au')], + 'multiple slots': [mockGptSlot('mock/gpt/au'), mockGptSlot('mock/gpt/au2')] + }).forEach(([t, gptSlots]) => { + describe(`when ad unit code matches ${t}`, () => { + it('should set GPT slot config', () => { + setGptConfig('au', gptSlots, [fledgeAuctionConfig]); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: fledgeAuctionConfig, + }] + }); + }) + }); + describe('when reset = true', () => { + it('should reset GPT slot config', () => { + setGptConfig('au', gptSlots, [fledgeAuctionConfig]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 'bidder', + auctionConfig: null + }] + }); + }) + }); + + it('should reset only sellers with no fresh config', () => { + setGptConfig('au', gptSlots, [{seller: 's1'}, {seller: 's2'}]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [{seller: 's1'}], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 's1', + auctionConfig: {seller: 's1'} + }, { + configKey: 's2', + auctionConfig: null + }] + }) + }) + }); + + it('should not reset sellers that were already reset', () => { + setGptConfig('au', gptSlots, [{seller: 's1'}]); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au', gptSlots, [], true); + gptSlots.forEach(slot => sinon.assert.notCalled(slot.setConfig)); + }) + + it('should keep track of configuration history by ad unit', () => { + setGptConfig('au1', gptSlots, [{seller: 's1'}]); + setGptConfig('au1', gptSlots, [{seller: 's2'}], false); + setGptConfig('au2', gptSlots, [{seller: 's3'}]); + gptSlots.forEach(slot => slot.setConfig.resetHistory()); + setGptConfig('au1', gptSlots, [], true); + gptSlots.forEach(slot => { + sinon.assert.calledWith(slot.setConfig, { + componentAuction: [{ + configKey: 's1', + auctionConfig: null + }, { + configKey: 's2', + auctionConfig: null + }] + }); + }) + }) + }); + }) + }) + }); + describe('setTargeting hook', () => { + let setPaapiConfig, setTargetingHook, next; + beforeEach(() => { + setPaapiConfig = sinon.stub() + setTargetingHook = setTargetingHookFactory(setPaapiConfig); + next = sinon.stub(); + }); + function expectFilters(...filters) { + expect(setPaapiConfig.args.length).to.eql(filters.length) + filters.forEach(filter => { + sinon.assert.calledWith(setPaapiConfig, filter, 'mock-matcher') + }) + } + function runHook(adUnit) { + setTargetingHook(next, adUnit, 'mock-matcher'); + sinon.assert.calledWith(next, adUnit, 'mock-matcher'); + } + it('should invoke with no filters when adUnit is undef', () => { + runHook(); + expectFilters(undefined); + }); + it('should invoke once when adUnit is a string', () => { + runHook('mock-au'); + expectFilters({adUnitCode: 'mock-au'}) + }); + it('should invoke once per ad unit when an array', () => { + runHook(['au1', 'au2']); + expectFilters({adUnitCode: 'au1'}, {adUnitCode: 'au2'}); + }) + }) + describe('setPAAPIConfigForGpt', () => { + let getPAAPIConfig, setGptConfig, getSlots, setPAAPIConfigForGPT; + beforeEach(() => { + getPAAPIConfig = sinon.stub(); + setGptConfig = sinon.stub(); + getSlots = sinon.stub().callsFake((codes) => Object.fromEntries(codes.map(code => [code, ['mock-slot']]))) + setPAAPIConfigForGPT = setPAAPIConfigFactory(getPAAPIConfig, setGptConfig, getSlots); + }); + + Object.entries({ + missing: null, + empty: {} + }).forEach(([t, configs]) => { + it(`does not set GPT slot config when config is ${t}`, () => { + getPAAPIConfig.returns(configs); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + sinon.assert.notCalled(setGptConfig); + }) + }); + + it('passes customSlotMatching to getSlots', () => { + getPAAPIConfig.returns({au1: {}}); + setPAAPIConfigForGPT('mock-filters', 'mock-custom-matching'); + sinon.assert.calledWith(getSlots, ['au1'], 'mock-custom-matching'); + }) + + it('sets GPT slot config for each ad unit that has PAAPI config, and resets the rest', () => { + const cfg = { + au1: { + componentAuctions: [{seller: 's1'}, {seller: 's2'}] + }, + au2: { + componentAuctions: [{seller: 's3'}] + }, + au3: null + } + getPAAPIConfig.returns(cfg); + setPAAPIConfigForGPT('mock-filters'); + sinon.assert.calledWith(getPAAPIConfig, 'mock-filters'); + Object.entries(cfg).forEach(([au, config]) => { + sinon.assert.calledWith(setGptConfig, au, ['mock-slot'], config?.componentAuctions ?? [], true); + }) + }); + }); + + describe('getPAAPISizeHook', () => { + let next; + beforeEach(() => { + next = sinon.stub(); + next.bail = sinon.stub(); + }); + + it('should pick largest supported size over larger unsupported size', () => { + getPAAPISizeHook(next, [[999, 999], [300, 250], [300, 600], [1234, 4321]]); + sinon.assert.calledWith(next.bail, [300, 600]); + }); + + Object.entries({ + 'present': [], + 'supported': [[123, 4], [321, 5]], + 'defined': undefined, + }).forEach(([t, sizes]) => { + it(`should defer to next when no size is ${t}`, () => { + getPAAPISizeHook(next, sizes); + sinon.assert.calledWith(next, sizes); + }) + }) + }) +}); diff --git a/test/spec/modules/paapi_spec.js b/test/spec/modules/paapi_spec.js index c7d6d88bd12..c41419074ad 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -2,20 +2,32 @@ import {expect} from 'chai'; import {config} from '../../../src/config.js'; import adapterManager from '../../../src/adapterManager.js'; import * as utils from '../../../src/utils.js'; +import {deepAccess, deepClone} from '../../../src/utils.js'; import {hook} from '../../../src/hook.js'; import 'modules/appnexusBidAdapter.js'; import 'modules/rubiconBidAdapter.js'; import { - addComponentAuctionHook, + addPaapiConfigHook, + addPaapiData, + ASYNC_SIGNALS, + buyersToAuctionConfigs, getPAAPIConfig, + getPAAPISize, + IGB_TO_CONFIG, + mergeBuyers, + onAuctionInit, + parallelPaapiProcessing, + parseExtIgi, parseExtPrebidFledge, + partitionBuyers, + partitionBuyersByBidder, registerSubmodule, + reset, setImpExtAe, - setResponseFledgeConfigs, - reset + setResponsePaapiConfigs } from 'modules/paapi.js'; import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; @@ -32,299 +44,387 @@ describe('paapi module', () => { reset(); }); - [ - 'fledgeForGpt', - 'paapi' - ].forEach(configNS => { - describe(`using ${configNS} for configuration`, () => { - describe('getPAAPIConfig', function () { - let nextFnSpy, fledgeAuctionConfig; - before(() => { - config.setConfig({[configNS]: {enabled: true}}); + describe(`using paapi configuration`, () => { + let getPAAPISizeStub; + + function getPAAPISizeHook(next, sizes) { + next.bail(getPAAPISizeStub(sizes)); + } + + before(() => { + getPAAPISize.before(getPAAPISizeHook, 100); + }); + + after(() => { + getPAAPISize.getHooks({hook: getPAAPISizeHook}).remove(); + }); + + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + }); + + describe('getPAAPIConfig', function () { + let nextFnSpy, auctionConfig, paapiConfig; + before(() => { + config.setConfig({paapi: {enabled: true}}); + }); + beforeEach(() => { + auctionConfig = { + seller: 'bidder', + mock: 'config' + }; + paapiConfig = { + config: auctionConfig + }; + nextFnSpy = sinon.spy(); + }); + + describe('on a single auction', function () { + const auctionId = 'aid'; + beforeEach(function () { + sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); }); - beforeEach(() => { - fledgeAuctionConfig = { - seller: 'bidder', - mock: 'config' - }; - nextFnSpy = sinon.spy(); + + it('should call next()', function () { + const request = {auctionId, adUnitCode: 'auc'}; + addPaapiConfigHook(nextFnSpy, request, paapiConfig); + sinon.assert.calledWith(nextFnSpy, request, paapiConfig); }); - describe('on a single auction', function () { - const auctionId = 'aid'; - beforeEach(function () { - sandbox.stub(auctionManager, 'index').value(stubAuctionIndex({auctionId})); + describe('igb', () => { + let igb1, igb2, buyerAuctionConfig; + beforeEach(() => { + igb1 = { + origin: 'buyer.1' + }; + igb2 = { + origin: 'buyer.2' + }; + buyerAuctionConfig = { + seller: 'seller', + decisionLogicURL: 'seller-decision-logic' + }; + config.mergeConfig({ + paapi: { + componentSeller: { + auctionConfig: buyerAuctionConfig + } + } + }); }); - it('should call next()', function () { - const request = {auctionId, adUnitCode: 'auc'}; - addComponentAuctionHook(nextFnSpy, request, fledgeAuctionConfig); - sinon.assert.calledWith(nextFnSpy, request, fledgeAuctionConfig); + function addIgb(request, igb) { + addPaapiConfigHook(nextFnSpy, Object.assign({auctionId}, request), {igb}); + } + + it('should be collected into an auction config', () => { + addIgb({adUnitCode: 'au1'}, igb1); + addIgb({adUnitCode: 'au1'}, igb2); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + const buyerConfig = getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + sinon.assert.match(buyerConfig, { + interestGroupBuyers: [igb1.origin, igb2.origin], + ...buyerAuctionConfig + }); }); - describe('should collect auction configs', () => { - let cf1, cf2; + describe('FPD', () => { + let ortb2, ortb2Imp; beforeEach(() => { - cf1 = {...fledgeAuctionConfig, id: 1, seller: 'b1'}; - cf2 = {...fledgeAuctionConfig, id: 2, seller: 'b2'}; - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, cf1); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, cf2); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); + ortb2 = {'fpd': 1}; + ortb2Imp = {'fpd': 2}; }); - it('and make them available at end of auction', () => { - sinon.assert.match(getPAAPIConfig({auctionId}), { - au1: { - componentAuctions: [cf1] - }, - au2: { - componentAuctions: [cf2] + function getBuyerAuctionConfig() { + addIgb({adUnitCode: 'au1', ortb2, ortb2Imp}, igb1); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } + + it('should be added to auction config', () => { + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: { + ortb2, + ortb2Imp } }); }); - it('and filter them by ad unit', () => { - const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); - expect(Object.keys(cfg)).to.have.members(['au1']); - sinon.assert.match(cfg.au1, { - componentAuctions: [cf1] + it('should not override existing perBuyerSignals', () => { + const original = { + ortb2: { + fpd: 'original' + } + }; + igb1.pbs = { + prebid: deepClone(original) + }; + sinon.assert.match(getBuyerAuctionConfig().perBuyerSignals[igb1.origin], { + prebid: original }); }); + }); + }); + + describe('should collect auction configs', () => { + let cf1, cf2; + beforeEach(() => { + cf1 = {...auctionConfig, id: 1, seller: 'b1'}; + cf2 = {...auctionConfig, id: 2, seller: 'b2'}; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, {config: cf1}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, {config: cf2}); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + }); - it('and not return them again', () => { - getPAAPIConfig(); - const cfg = getPAAPIConfig(); - expect(cfg).to.eql({}); + it('and make them available at end of auction', () => { + sinon.assert.match(getPAAPIConfig({auctionId}), { + au1: { + componentAuctions: [cf1] + }, + au2: { + componentAuctions: [cf2] + } }); + }); - describe('includeBlanks = true', () => { - it('includes all ad units', () => { - const cfg = getPAAPIConfig({}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }) - it('includes the targeted adUnit', () => { - expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ - au3: null - }) - }); - it('includes the targeted auction', () => { - const cfg = getPAAPIConfig({auctionId}, true); - expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); - expect(cfg.au3).to.eql(null); - }); - it('does not include non-existing ad units', () => { - expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); - }); - it('does not include non-existing auctions', () => { - expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); - }) + it('and filter them by ad unit', () => { + const cfg = getPAAPIConfig({auctionId, adUnitCode: 'au1'}); + expect(Object.keys(cfg)).to.have.members(['au1']); + sinon.assert.match(cfg.au1, { + componentAuctions: [cf1] }); }); - it('should drop auction configs after end of auction', () => { - events.emit(EVENTS.AUCTION_END, { auctionId }); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); - expect(getPAAPIConfig({auctionId})).to.eql({}); + it('and not return them again', () => { + getPAAPIConfig(); + const cfg = getPAAPIConfig(); + expect(cfg).to.eql({}); }); - it('should use first size as requestedSize', () => { - addComponentAuctionHook(nextFnSpy, { - auctionId, - adUnitCode: 'au1', - }, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { - auctionId, - adUnits: [ - { - code: 'au1', - mediaTypes: { - banner: { - sizes: [[200, 100], [300, 200]] - } - } - } - ] + describe('includeBlanks = true', () => { + it('includes all ad units', () => { + const cfg = getPAAPIConfig({}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); }); - expect(getPAAPIConfig({auctionId}).au1.requestedSize).to.eql({ - width: '200', - height: '100' - }) - }) + it('includes the targeted adUnit', () => { + expect(getPAAPIConfig({adUnitCode: 'au3'}, true)).to.eql({ + au3: null + }); + }); + it('includes the targeted auction', () => { + const cfg = getPAAPIConfig({auctionId}, true); + expect(Object.keys(cfg)).to.have.members(['au1', 'au2', 'au3']); + expect(cfg.au3).to.eql(null); + }); + it('does not include non-existing ad units', () => { + expect(getPAAPIConfig({adUnitCode: 'other'})).to.eql({}); + }); + it('does not include non-existing auctions', () => { + expect(getPAAPIConfig({auctionId: 'other'})).to.eql({}); + }); + }); + }); + + it('should drop auction configs after end of auction', () => { + events.emit(EVENTS.AUCTION_END, {auctionId}); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + expect(getPAAPIConfig({auctionId})).to.eql({}); + }); + + describe('FPD', () => { + let ortb2, ortb2Imp; + beforeEach(() => { + ortb2 = {fpd: 1}; + ortb2Imp = {fpd: 2}; + }); - it('should augment auctionSignals with FPD', () => { - addComponentAuctionHook(nextFnSpy, { + function getComponentAuctionConfig() { + addPaapiConfigHook(nextFnSpy, { auctionId, adUnitCode: 'au1', ortb2: {fpd: 1}, ortb2Imp: {fpd: 2} - }, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId }); - sinon.assert.match(getPAAPIConfig({auctionId}), { - au1: { - componentAuctions: [{ - ...fledgeAuctionConfig, - auctionSignals: { - prebid: { - ortb2: {fpd: 1}, - ortb2Imp: {fpd: 2} - } - } - }] + }, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId}); + return getPAAPIConfig({auctionId}).au1.componentAuctions[0]; + } + + it('should be added to auctionSignals', () => { + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: {ortb2, ortb2Imp} + }); + }); + it('should not override existing auctionSignals', () => { + auctionConfig.auctionSignals = {prebid: {ortb2: {fpd: 'original'}}}; + sinon.assert.match(getComponentAuctionConfig().auctionSignals, { + prebid: { + ortb2: {fpd: 'original'}, + ortb2Imp } }); }); - describe('submodules', () => { - let submods; - beforeEach(() => { - submods = [1, 2].map(i => ({ - name: `test${i}`, - onAuctionConfig: sinon.stub() - })); - submods.forEach(registerSubmodule); - }); - - describe('onAuctionConfig', () => { - const auctionId = 'aid'; - it('is invoked with null configs when there\'s no config', () => { - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au'] }); - submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); - }); - it('is invoked with relevant configs', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1', 'au2', 'au3'] }); - submods.forEach(submod => { - sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { - au1: {componentAuctions: [fledgeAuctionConfig]}, - au2: {componentAuctions: [fledgeAuctionConfig]}, - au3: null - }) - }); - }); - it('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { - submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { - markAsUsed('au1'); - }); - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); - expect(getPAAPIConfig()).to.eql({}); - }); - it('keeps them available if they do not', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: ['au1'] }); - expect(getPAAPIConfig()).to.not.be.empty; - }) + it('should be added to perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer.1', 'buyer.2']; + const pbs = getComponentAuctionConfig().perBuyerSignals; + sinon.assert.match(pbs, { + 'buyer.1': {prebid: {ortb2, ortb2Imp}}, + 'buyer.2': {prebid: {ortb2, ortb2Imp}} }); }); - describe('floor signal', () => { - before(() => { - if (!getGlobal().convertCurrency) { - getGlobal().convertCurrency = () => null; - getGlobal().convertCurrency.mock = true; + it('should not override existing perBuyerSignals', () => { + auctionConfig.interestGroupBuyers = ['buyer']; + const original = { + prebid: { + ortb2: { + fpd: 'original' + } } + }; + auctionConfig.perBuyerSignals = { + buyer: deepClone(original) + }; + sinon.assert.match(getComponentAuctionConfig().perBuyerSignals.buyer, original); + }); + }); + + describe('submodules', () => { + let submods; + beforeEach(() => { + submods = [1, 2].map(i => ({ + name: `test${i}`, + onAuctionConfig: sinon.stub() + })); + submods.forEach(registerSubmodule); + }); + + describe('onAuctionConfig', () => { + const auctionId = 'aid'; + it('is invoked with null configs when there\'s no config', () => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au']}); + submods.forEach(submod => sinon.assert.calledWith(submod.onAuctionConfig, auctionId, {au: null})); }); - after(() => { - if (getGlobal().convertCurrency.mock) { - delete getGlobal().convertCurrency; - } + it('is invoked with relevant configs', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au2'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1', 'au2', 'au3']}); + submods.forEach(submod => { + sinon.assert.calledWith(submod.onAuctionConfig, auctionId, { + au1: {componentAuctions: [auctionConfig]}, + au2: {componentAuctions: [auctionConfig]}, + au3: null + }); + }); }); + }); + }); - beforeEach(() => { - sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { - if (from === to) return amount; - if (from === 'USD' && to === 'JPY') return amount * 100; - if (from === 'JPY' && to === 'USD') return amount / 100; - throw new Error('unexpected currency conversion'); - }); + describe('floor signal', () => { + before(() => { + if (!getGlobal().convertCurrency) { + getGlobal().convertCurrency = () => null; + getGlobal().convertCurrency.mock = true; + } + }); + after(() => { + if (getGlobal().convertCurrency.mock) { + delete getGlobal().convertCurrency; + } + }); + + beforeEach(() => { + sandbox.stub(getGlobal(), 'convertCurrency').callsFake((amount, from, to) => { + if (from === to) return amount; + if (from === 'USD' && to === 'JPY') return amount * 100; + if (from === 'JPY' && to === 'USD') return amount / 100; + throw new Error('unexpected currency conversion'); }); + }); - Object.entries({ - 'bids': (payload, values) => { - payload.bidsReceived = values - .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) - .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); - }, - 'no bids': (payload, values) => { - payload.bidderRequests = values - .map((val) => ({ - bids: [{ - adUnitCode: 'au', - getFloor: () => ({floor: val.amount, currency: val.cur}) - }] - })) - .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); - } - }).forEach(([tcase, setup]) => { - describe(`when auction has ${tcase}`, () => { - Object.entries({ - 'no currencies': { - values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], - 'bids': { - bidfloor: 100, - bidfloorcur: undefined - }, - 'no bids': { - bidfloor: 1, - bidfloorcur: undefined, - } + Object.entries({ + 'bids': (payload, values) => { + payload.bidsReceived = values + .map((val) => ({adUnitCode: 'au', cpm: val.amount, currency: val.cur})) + .concat([{adUnitCode: 'other', cpm: 10000, currency: 'EUR'}]); + }, + 'no bids': (payload, values) => { + payload.bidderRequests = values + .map((val) => ({ + bids: [{ + adUnitCode: 'au', + getFloor: () => ({floor: val.amount, currency: val.cur}) + }] + })) + .concat([{bids: {adUnitCode: 'other', getFloor: () => ({floor: -10000, currency: 'EUR'})}}]); + } + }).forEach(([tcase, setup]) => { + describe(`when auction has ${tcase}`, () => { + Object.entries({ + 'no currencies': { + values: [{amount: 1}, {amount: 100}, {amount: 10}, {amount: 100}], + 'bids': { + bidfloor: 100, + bidfloorcur: undefined }, - 'only zero values': { - values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], - 'bids': { - bidfloor: undefined, - bidfloorcur: undefined, - }, - 'no bids': { - bidfloor: undefined, - bidfloorcur: undefined, - } + 'no bids': { + bidfloor: 1, + bidfloorcur: undefined, + } + }, + 'only zero values': { + values: [{amount: 0, cur: 'USD'}, {amount: 0, cur: 'JPY'}], + 'bids': { + bidfloor: undefined, + bidfloorcur: undefined, }, - 'matching currencies': { - values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], - 'bids': { - bidfloor: 100, - bidfloorcur: 'JPY', - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: undefined, + bidfloorcur: undefined, + } + }, + 'matching currencies': { + values: [{amount: 10, cur: 'JPY'}, {amount: 100, cur: 'JPY'}], + 'bids': { + bidfloor: 100, + bidfloorcur: 'JPY', }, - 'mixed currencies': { - values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], - 'bids': { - bidfloor: 10, - bidfloorcur: 'USD' - }, - 'no bids': { - bidfloor: 10, - bidfloorcur: 'JPY', - } + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', + } + }, + 'mixed currencies': { + values: [{amount: 10, cur: 'USD'}, {amount: 10, cur: 'JPY'}], + 'bids': { + bidfloor: 10, + bidfloorcur: 'USD' + }, + 'no bids': { + bidfloor: 10, + bidfloorcur: 'JPY', } - }).forEach(([t, testConfig]) => { - const values = testConfig.values; - const {bidfloor, bidfloorcur} = testConfig[tcase]; - - describe(`with ${t}`, () => { - let payload; - beforeEach(() => { - payload = {auctionId}; - setup(payload, values); - }); - - it('should populate bidfloor/bidfloorcur', () => { - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, fledgeAuctionConfig); - events.emit(EVENTS.AUCTION_END, payload); - const cfg = getPAAPIConfig({auctionId}).au; - const signals = cfg.auctionSignals; - sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); - expect(signals?.prebid?.bidfloor).to.eql(bidfloor); - expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); - }); + } + }).forEach(([t, testConfig]) => { + const values = testConfig.values; + const {bidfloor, bidfloorcur} = testConfig[tcase]; + + describe(`with ${t}`, () => { + let payload; + beforeEach(() => { + payload = {auctionId}; + setup(payload, values); + }); + + it('should populate bidfloor/bidfloorcur', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, payload); + const cfg = getPAAPIConfig({auctionId}).au; + const signals = cfg.auctionSignals; + sinon.assert.match(cfg.componentAuctions[0].auctionSignals, signals || {}); + expect(signals?.prebid?.bidfloor).to.eql(bidfloor); + expect(signals?.prebid?.bidfloorcur).to.eql(bidfloorcur); }); }); }); @@ -332,136 +432,195 @@ describe('paapi module', () => { }); }); - describe('with multiple auctions', () => { - const AUCTION1 = 'auction1'; - const AUCTION2 = 'auction2'; - - function mockAuction(auctionId) { - return { - getAuctionId() { - return auctionId; - } + describe('requestedSize', () => { + let adUnit; + beforeEach(() => { + adUnit = { + code: 'au', }; - } + }); - function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { - expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); - Object.entries(actualConfig).forEach(([au, cfg]) => { - cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); - }); + function getConfig() { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: adUnit.code}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: [adUnit.code], adUnits: [adUnit]}); + return getPAAPIConfig()[adUnit.code]; } - let configs; - beforeEach(() => { - const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; - sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); - configs = {[AUCTION1]: {}, [AUCTION2]: {}}; - Object.entries({ - [AUCTION1]: [['au1', 'au2'], ['missing-1']], - [AUCTION2]: [['au2', 'au3'], []], - }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { - adUnitCodes.forEach(adUnitCode => { - const cfg = {...fledgeAuctionConfig, auctionId, adUnitCode}; - configs[auctionId][adUnitCode] = cfg; - addComponentAuctionHook(nextFnSpy, {auctionId, adUnitCode}, cfg); + Object.entries({ + 'adUnit.ortb2Imp.ext.paapi.requestedSize'() { + adUnit.ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + }; + }, + 'largest size'() { + getPAAPISizeStub.returns([123, 321]); + } + }).forEach(([t, setup]) => { + describe(`should be set from ${t}`, () => { + beforeEach(setup); + + it('without overriding component auctions, if set', () => { + auctionConfig.requestedSize = {width: '1px', height: '2px'}; + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: '1px', + height: '2px' + }); + }); + + it('on component auction, if missing', () => { + expect(getConfig().componentAuctions[0].requestedSize).to.eql({ + width: 123, + height: 321 + }); + }); + + it('on top level auction', () => { + expect(getConfig().requestedSize).to.eql({ + width: 123, + height: 321, + }); }); - events.emit(EVENTS.AUCTION_END, { auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes) }); }); }); + }); + }); - it('should filter by auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); - }); + describe('with multiple auctions', () => { + const AUCTION1 = 'auction1'; + const AUCTION2 = 'auction2'; - it('should filter by auction and ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); - }); + function mockAuction(auctionId) { + return { + getAuctionId() { + return auctionId; + } + }; + } - it('should use last auction for each ad unit', () => { - expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + function expectAdUnitsFromAuctions(actualConfig, auToAuctionMap) { + expect(Object.keys(actualConfig)).to.have.members(Object.keys(auToAuctionMap)); + Object.entries(actualConfig).forEach(([au, cfg]) => { + cfg.componentAuctions.forEach(cmp => expect(cmp.auctionId).to.eql(auToAuctionMap[au])); }); + } - it('should filter by ad unit and use latest auction', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); + let configs; + beforeEach(() => { + const mockAuctions = [mockAuction(AUCTION1), mockAuction(AUCTION2)]; + sandbox.stub(auctionManager, 'index').value(new AuctionIndex(() => mockAuctions)); + configs = {[AUCTION1]: {}, [AUCTION2]: {}}; + Object.entries({ + [AUCTION1]: [['au1', 'au2'], ['missing-1']], + [AUCTION2]: [['au2', 'au3'], []], + }).forEach(([auctionId, [adUnitCodes, noConfigAdUnitCodes]]) => { + adUnitCodes.forEach(adUnitCode => { + const cfg = {...auctionConfig, auctionId, adUnitCode}; + configs[auctionId][adUnitCode] = cfg; + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode}, {config: cfg}); + }); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: adUnitCodes.concat(noConfigAdUnitCodes)}); }); + }); - it('should keep track of which configs were returned', () => { - expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); - expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); - expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); - }); + it('should filter by auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2}), {au2: AUCTION2, au3: AUCTION2}); + }); - describe('includeBlanks = true', () => { - Object.entries({ - 'auction with blanks': { - filters: {auctionId: AUCTION1}, - expected: {au1: true, au2: true, 'missing-1': false} - }, - 'blank adUnit in an auction': { - filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, - expected: {'missing-1': false} - }, - 'non-existing auction': { - filters: {auctionId: 'other'}, - expected: {} - }, - 'non-existing adUnit in an auction': { - filters: {auctionId: AUCTION2, adUnitCode: 'other'}, - expected: {} - }, - 'non-existing ad unit': { - filters: {adUnitCode: 'other'}, - expected: {}, - }, - 'non existing ad unit in a non-existing auction': { - filters: {adUnitCode: 'other', auctionId: 'other'}, - expected: {} - }, - 'all ad units': { - filters: {}, - expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} - } - }).forEach(([t, {filters, expected}]) => { - it(t, () => { - const cfg = getPAAPIConfig(filters, true); - expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); - Object.entries(expected).forEach(([au, shouldBeFilled]) => { - if (shouldBeFilled) { - expect(cfg[au]).to.not.be.null; - } else { - expect(cfg[au]).to.be.null; - } - }) - }) - }) - }); + it('should filter by auction and ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1, adUnitCode: 'au2'}), {au2: AUCTION1}); + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION2, adUnitCode: 'au2'}), {au2: AUCTION2}); }); - }); - describe('markForFledge', function () { - const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + it('should use last auction for each ad unit', () => { + expectAdUnitsFromAuctions(getPAAPIConfig(), {au1: AUCTION1, au2: AUCTION2, au3: AUCTION2}); + }); - before(function () { - // navigator.runAdAuction & co may not exist, so we can't stub it normally with - // sinon.stub(navigator, 'runAdAuction') or something - Object.keys(navProps).forEach(p => { - navigator[p] = sinon.stub(); - }); - hook.ready(); - config.resetConfig(); + it('should filter by ad unit and use latest auction', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({adUnitCode: 'au2'}), {au2: AUCTION2}); }); - after(function () { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + it('should keep track of which configs were returned', () => { + expectAdUnitsFromAuctions(getPAAPIConfig({auctionId: AUCTION1}), {au1: AUCTION1, au2: AUCTION1}); + expect(getPAAPIConfig({auctionId: AUCTION1})).to.eql({}); + expectAdUnitsFromAuctions(getPAAPIConfig(), {au2: AUCTION2, au3: AUCTION2}); + }); + + describe('includeBlanks = true', () => { + Object.entries({ + 'auction with blanks': { + filters: {auctionId: AUCTION1}, + expected: {au1: true, au2: true, 'missing-1': false} + }, + 'blank adUnit in an auction': { + filters: {auctionId: AUCTION1, adUnitCode: 'missing-1'}, + expected: {'missing-1': false} + }, + 'non-existing auction': { + filters: {auctionId: 'other'}, + expected: {} + }, + 'non-existing adUnit in an auction': { + filters: {auctionId: AUCTION2, adUnitCode: 'other'}, + expected: {} + }, + 'non-existing ad unit': { + filters: {adUnitCode: 'other'}, + expected: {}, + }, + 'non existing ad unit in a non-existing auction': { + filters: {adUnitCode: 'other', auctionId: 'other'}, + expected: {} + }, + 'all ad units': { + filters: {}, + expected: {'au1': true, 'au2': true, 'missing-1': false, 'au3': true} + } + }).forEach(([t, {filters, expected}]) => { + it(t, () => { + const cfg = getPAAPIConfig(filters, true); + expect(Object.keys(cfg)).to.have.members(Object.keys(expected)); + Object.entries(expected).forEach(([au, shouldBeFilled]) => { + if (shouldBeFilled) { + expect(cfg[au]).to.not.be.null; + } else { + expect(cfg[au]).to.be.null; + } + }); + }); + }); }); + }); + }); + + describe('markForFledge', function () { + const navProps = Object.fromEntries(['runAdAuction', 'joinAdInterestGroup'].map(p => [p, navigator[p]])); + let adUnits; - afterEach(function () { - config.resetConfig(); + before(function () { + // navigator.runAdAuction & co may not exist, so we can't stub it normally with + // sinon.stub(navigator, 'runAdAuction') or something + Object.keys(navProps).forEach(p => { + navigator[p] = sinon.stub(); }); + hook.ready(); + config.resetConfig(); + }); + + after(function () { + Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + }); - const adUnits = [{ + beforeEach(() => { + getPAAPISizeStub = sinon.stub(); + adUnits = [{ 'code': '/19968336/header-bid-tag1', 'mediaTypes': { 'banner': { @@ -477,9 +636,15 @@ describe('paapi module', () => { }, ] }]; + }); - function expectFledgeFlags(...enableFlags) { - const bidRequests = Object.fromEntries( + afterEach(function () { + config.resetConfig(); + }); + + describe('makeBidRequests', () => { + function mark() { + return Object.fromEntries( adapterManager.makeBidRequests( adUnits, Date.now(), @@ -489,44 +654,43 @@ describe('paapi module', () => { [] ).map(b => [b.bidderCode, b]) ); + } - expect(bidRequests.appnexus.fledgeEnabled).to.eql(enableFlags[0].enabled); + function expectFledgeFlags(...enableFlags) { + const bidRequests = mark(); + expect(bidRequests.appnexus.paapi?.enabled).to.eql(enableFlags[0].enabled); bidRequests.appnexus.bids.forEach(bid => expect(bid.ortb2Imp.ext.ae).to.eql(enableFlags[0].ae)); - expect(bidRequests.rubicon.fledgeEnabled).to.eql(enableFlags[1].enabled); + expect(bidRequests.rubicon.paapi?.enabled).to.eql(enableFlags[1].enabled); bidRequests.rubicon.bids.forEach(bid => expect(bid.ortb2Imp?.ext?.ae).to.eql(enableFlags[1].ae)); - } - describe('with setBidderConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { - config.setBidderConfig({ - bidders: ['appnexus'], - config: { - defaultForSlots: 1, - fledgeEnabled: true - } - }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: void 0, ae: void 0}); + Object.values(bidRequests).flatMap(req => req.bids).forEach(bid => { + if (bid.ortb2Imp?.ext?.ae) { + sinon.assert.match(bid.ortb2Imp.ext.igs, { + ae: bid.ortb2Imp.ext.ae, + biddable: 1 + }); + } }); - }); + } describe('with setConfig()', () => { - it('should set fledgeEnabled correctly per bidder', function () { + it('should set paapi.enabled correctly per bidder', function () { config.setConfig({ bidderSequence: 'fixed', - [configNS]: { + paapi: { enabled: true, bidders: ['appnexus'], defaultForSlots: 1, } }); - expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: undefined}); + expectFledgeFlags({enabled: true, ae: 1}, {enabled: false, ae: 0}); }); - it('should set fledgeEnabled correctly for all bidders', function () { + it('should set paapi.enabled correctly for all bidders', function () { config.setConfig({ bidderSequence: 'fixed', - [configNS]: { + paapi: { enabled: true, defaultForSlots: 1, } @@ -534,115 +698,1154 @@ describe('paapi module', () => { expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); - it('should not override pub-defined ext.ae', () => { - config.setConfig({ - bidderSequence: 'fixed', - [configNS]: { - enabled: true, - defaultForSlots: 1, - } + Object.entries({ + 'not set': { + cfg: {}, + componentSeller: false + }, + 'set': { + cfg: { + componentSeller: { + auctionConfig: { + decisionLogicURL: 'publisher.example' + } + } + }, + componentSeller: true + } + }).forEach(([t, {cfg, componentSeller}]) => { + it(`should set request paapi.componentSeller = ${componentSeller} when config componentSeller is ${t}`, () => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + ...cfg + } + }); + Object.values(mark()).forEach(br => expect(br.paapi?.componentSeller).to.eql(componentSeller)); }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); }); }); }); - }); - }); + describe('addPaapiData', () => { + function getEnrichedAdUnits() { + const next = sinon.stub(); + addPaapiData(next, adUnits); + sinon.assert.calledWith(next, adUnits); + return adUnits; + } - describe('ortb processors for fledge', () => { - it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; - setImpExtAe(imp, {}, {bidderRequest: {}}); - expect(imp.ext.ae).to.not.exist; - }); - it('imp.ext.ae should be left intact if fledge is enabled', () => { - const imp = {ext: {ae: 2}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); - }); - describe('parseExtPrebidFledge', () => { - function packageConfigs(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } + function getImpExt() { + const next = sinon.stub(); + addPaapiData(next, adUnits); + sinon.assert.calledWith(next, adUnits); + return { + global: adUnits[0].ortb2Imp?.ext, + ...Object.fromEntries(adUnits[0].bids.map(bid => [bid.bidder, bid.ortb2Imp?.ext])) } - }; - } - - function generateImpCtx(fledgeFlags) { - return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); - } + } - function generateCfg(impid, ...ids) { - return ids.map((id) => ({impid, config: {id}})); - } + it('should not override pub-defined ext.ae', () => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); + sinon.assert.match(getImpExt(), { + global: { + ae: 0, + }, + rubicon: undefined, + appnexus: undefined + }); + }); - function extractResult(ctx) { - return Object.fromEntries( + it('should override per-bidder when excluded via paapi.bidders', () => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + bidders: ['rubicon'] + } + }) + sinon.assert.match(getImpExt(), { + global: { + ae: 1, + igs: { + ae: 1, + biddable: 1 + } + }, + rubicon: undefined, + appnexus: { + ae: 0, + igs: { + ae: 0, + biddable: 0 + } + } + }) + }) + + it('should populate ext.igs when request has ext.ae', () => { + config.setConfig({ + paapi: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 3}}}); + sinon.assert.match(getImpExt(), { + global: { + ae: 3, + igs: { + ae: 3, + biddable: 1 + } + }, + rubicon: undefined, + appnexus: undefined, + }); + }); + + it('should not override pub-defined ext.igs', () => { + config.setConfig({ + paapi: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 1, igs: {biddable: 0}}}}); + sinon.assert.match(getImpExt(), { + global: { + ae: 1, + igs: { + ae: 1, + biddable: 0 + } + }, + rubicon: undefined, + appnexus: undefined + }) + }); + + it('should fill ext.ae from ext.igs, if defined', () => { + config.setConfig({ + paapi: { + enabled: true + } + }); + Object.assign(adUnits[0], {ortb2Imp: {ext: {igs: {}}}}); + sinon.assert.match(getImpExt(), { + global: { + ae: 1, + igs: { + ae: 1, + biddable: 1 + } + }, + appnexus: undefined, + rubicon: undefined + }) + }); + + describe('ortb2Imp.ext.paapi.requestedSize', () => { + beforeEach(() => { + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1, + } + }); + }); + + it('should default to value returned by getPAAPISize', () => { + getPAAPISizeStub.returns([123, 321]); + expect(getImpExt().global.paapi).to.eql({ + requestedSize: { + width: 123, + height: 321 + } + }); + }); + + it('should not be overridden, if provided by the pub', () => { + adUnits[0].ortb2Imp = { + ext: { + paapi: { + requestedSize: { + width: '123px', + height: '321px' + } + } + } + }; + expect(getImpExt().global.paapi).to.eql({ + requestedSize: { + width: '123px', + height: '321px' + } + }) + sinon.assert.notCalled(getPAAPISizeStub); + }); + + it('should not be set if adUnit has no banner sizes', () => { + adUnits[0].mediaTypes = { + video: {} + }; + expect(getImpExt().global?.paapi?.requestedSize).to.not.exist; + }); + }); + }); + }); + }); + + describe('igb', () => { + let igb1, igb2; + const buyer1 = 'https://buyer1.example'; + const buyer2 = 'https://buyer2.example'; + beforeEach(() => { + igb1 = { + origin: buyer1, + cur: 'EUR', + maxbid: 1, + pbs: { + signal: 1 + }, + ps: { + priority: 1 + } + }; + igb2 = { + origin: buyer2, + cur: 'USD', + maxbid: 2, + pbs: { + signal: 2 + }, + ps: { + priority: 2 + } + }; + }); + + describe('mergeBuyers', () => { + it('should merge multiple igb into a partial auction config', () => { + sinon.assert.match(mergeBuyers([igb1, igb2]), { + interestGroupBuyers: [buyer1, buyer2], + perBuyerCurrencies: { + [buyer1]: 'EUR', + [buyer2]: 'USD' + }, + perBuyerSignals: { + [buyer1]: { + signal: 1 + }, + [buyer2]: { + signal: 2 + } + }, + perBuyerPrioritySignals: { + [buyer1]: { + priority: 1 + }, + [buyer2]: { + priority: 2 + } + }, + auctionSignals: { + prebid: { + perBuyerMaxbid: { + [buyer1]: 1, + [buyer2]: 2 + } + } + } + }); + }); + + Object.entries(IGB_TO_CONFIG).forEach(([igbField, configField]) => { + it(`should not set ${configField} if ${igbField} is undefined`, () => { + delete igb1[igbField]; + expect(deepAccess(mergeBuyers([igb1, igb2]), configField)[buyer1]).to.not.exist; + }); + }); + + it('ignores igbs that have no origin', () => { + delete igb1.origin; + expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb2])); + }); + + it('ignores igbs with duplicate origin', () => { + igb2.origin = igb1.origin; + expect(mergeBuyers([igb1, igb2])).to.eql(mergeBuyers([igb1])); + }); + }); + + describe('partitionBuyers', () => { + it('should return a single partition when there are no duplicates', () => { + expect(partitionBuyers([igb1, igb2])).to.eql([[igb1, igb2]]); + }); + it('should ignore igbs that have no origin', () => { + delete igb1.origin; + expect(partitionBuyers([igb1, igb2])).to.eql([[igb2]]); + }); + it('should return a single partition when duplicates exist, but do not conflict', () => { + expect(partitionBuyers([igb1, igb2, deepClone(igb1)])).to.eql([[igb1, igb2]]); + }); + it('should return multiple partitions when there are conflicts', () => { + const igb3 = deepClone(igb1); + const igb4 = deepClone(igb1); + igb3.pbs.signal = 'conflict'; + igb4.ps.signal = 'conflict'; + expect(partitionBuyers([igb1, igb2, igb3, igb4])).to.eql([ + [igb1, igb2], + [igb3], + [igb4] + ]); + }); + }); + + describe('partitionBuyersByBidder', () => { + it('should split requests by bidder', () => { + expect(partitionBuyersByBidder([[{bidder: 'a'}, igb1], [{bidder: 'b'}, igb2]])).to.eql([ + [{bidder: 'a'}, [igb1]], + [{bidder: 'b'}, [igb2]] + ]); + }); + + it('accepts repeated buyers, if from different bidders', () => { + expect(partitionBuyersByBidder([ + [{bidder: 'a', extra: 'data'}, igb1], + [{bidder: 'b', more: 'data'}, igb1], + [{bidder: 'a'}, igb2], + [{bidder: 'b'}, igb2] + ])).to.eql([ + [{bidder: 'a', extra: 'data'}, [igb1, igb2]], + [{bidder: 'b', more: 'data'}, [igb1, igb2]] + ]); + }); + describe('buyersToAuctionConfig', () => { + let config, partitioners, merge, igbRequests; + beforeEach(() => { + config = { + auctionConfig: { + decisionLogicURL: 'mock-decision-logic' + } + }; + partitioners = { + compact: sinon.stub(), + expand: sinon.stub(), + }; + let i = 0; + merge = sinon.stub().callsFake(() => ({config: i++})); + igbRequests = [ + [{}, igb1], + [{}, igb2] + ]; + }); + + function toAuctionConfig(reqs = igbRequests) { + return buyersToAuctionConfigs(reqs, merge, config, partitioners); + } + + it('uses compact partitions by default, and returns an auction config for each one', () => { + partitioners.compact.returns([[{}, 1], [{}, 2]]); + const [cf1, cf2] = toAuctionConfig(); + sinon.assert.match(cf1[1], { + ...config.auctionConfig, + config: 0 + }); + sinon.assert.match(cf2[1], { + ...config.auctionConfig, + config: 1 + }); + sinon.assert.calledWith(partitioners.compact, igbRequests); + [1, 2].forEach(mockPart => sinon.assert.calledWith(merge, mockPart)); + }); + + it('uses per-bidder partition when config has separateAuctions', () => { + config.separateAuctions = true; + partitioners.expand.returns([]); + toAuctionConfig(); + sinon.assert.called(partitioners.expand); + }); + + it('does not return any auction config when configuration does not specify auctionConfig', () => { + delete config.auctionConfig; + expect(toAuctionConfig()).to.eql([]); + Object.values(partitioners).forEach(part => sinon.assert.notCalled(part)); + }); + + it('sets FPD in auction signals when partitioner returns it', () => { + const fpd = { + ortb2: {fpd: 1}, + ortb2Imp: {fpd: 2} + }; + partitioners.compact.returns([[{}], [fpd]]); + const [cf1, cf2] = toAuctionConfig(); + expect(cf1[1].auctionSignals?.prebid).to.not.exist; + expect(cf2[1].auctionSignals.prebid).to.eql(fpd); + }); + }); + }); + }); + + describe('getPAAPISize', () => { + before(() => { + getPAAPISize.getHooks().remove(); + }); + + Object.entries({ + 'ignores placeholders': { + in: [[1, 1], [0, 0], [3, 4]], + out: [3, 4] + }, + 'picks largest size by area': { + in: [[200, 100], [150, 150]], + out: [150, 150] + }, + 'can handle no sizes': { + in: [], + out: undefined + }, + 'can handle no input': { + in: undefined, + out: undefined + }, + 'can handle placeholder sizes': { + in: [[1, 1]], + out: undefined + } + }).forEach(([t, {in: input, out}]) => { + it(t, () => { + expect(getPAAPISize(input)).to.eql(out); + }); + }); + }); + + describe('parallel PAAPI auctions', () => { + describe('parallellPaapiProcessing', () => { + let next, spec, bids, bidderRequest, restOfTheArgs, mockConfig, mockAuction, bidsReceived, bidderRequests, adUnitCodes, adUnits; + + beforeEach(() => { + next = sinon.stub(); + spec = { + code: 'mockBidder', + }; + bids = [{ + bidder: 'mockBidder', + bidId: 'bidId', + adUnitCode: 'au', + auctionId: 'aid', + mediaTypes: { + banner: { + sizes: [[123, 321]] + } + } + }]; + bidderRequest = {auctionId: 'aid', bidderCode: 'mockBidder', paapi: {enabled: true}, bids}; + restOfTheArgs = [{more: 'args'}]; + mockConfig = { + seller: 'mock.seller', + decisionLogicURL: 'mock.seller/decisionLogic', + interestGroupBuyers: ['mock.buyer'] + } + mockAuction = {}; + bidsReceived = [{adUnitCode: 'au', cpm: 1}]; + adUnits = [{code: 'au'}] + adUnitCodes = ['au']; + bidderRequests = [bidderRequest]; + sandbox.stub(auctionManager.index, 'getAuction').callsFake(() => mockAuction); + sandbox.stub(auctionManager.index, 'getAdUnit').callsFake((req) => bids.find(bid => bid.adUnitCode === req.adUnitCode)) + config.setConfig({paapi: {enabled: true}}); + }); + + afterEach(() => { + sinon.assert.calledWith(next, spec, bids, bidderRequest, ...restOfTheArgs); + config.resetConfig(); + }); + + function startParallel() { + parallelPaapiProcessing(next, spec, bids, bidderRequest, ...restOfTheArgs); + onAuctionInit({auctionId: 'aid'}) + } + + function endAuction() { + events.emit(EVENTS.AUCTION_END, {auctionId: 'aid', bidsReceived, bidderRequests, adUnitCodes, adUnits}) + } + + describe('should have no effect when', () => { + afterEach(() => { + expect(getPAAPIConfig({}, true)).to.eql({au: null}); + }) + it('spec has no buildPAAPIConfigs', () => { + startParallel(); + }); + Object.entries({ + 'returns no configs': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); }, + 'throws': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => { throw new Error() }) }, + 'returns too little config': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [ {bidId: 'bidId', config: {seller: 'mock.seller'}} ]) }, + 'bidder is not paapi enabled': () => { + bidderRequest.paapi.enabled = false; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'bidId'}]) + }, + 'paapi module is not enabled': () => { + delete bidderRequest.paapi; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'bidId'}]) + }, + 'bidId points to missing bid': () => { spec.buildPAAPIConfigs = sinon.stub().callsFake(() => [{config: mockConfig, bidId: 'missing'}]) } + }).forEach(([t, setup]) => { + it(`buildPAAPIConfigs ${t}`, () => { + setup(); + startParallel(); + }); + }); + }); + + function resolveConfig(auctionConfig) { + return Promise.all( + Object.entries(auctionConfig) + .map(([key, value]) => Promise.resolve(value).then(value => [key, value])) + ).then(result => Object.fromEntries(result)) + } + + describe('when buildPAAPIConfigs returns valid config', () => { + let builtCfg; + beforeEach(() => { + builtCfg = [{bidId: 'bidId', config: mockConfig}]; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); + }); + + it('should make async config available from getPAAPIConfig', () => { + startParallel(); + const actual = getPAAPIConfig(); + const promises = Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match((arg) => arg instanceof Promise)])) + sinon.assert.match(actual, { + au: sinon.match({ + ...promises, + requestedSize: { + width: 123, + height: 321 + }, + componentAuctions: [ + sinon.match({ + ...mockConfig, + ...promises, + requestedSize: { + width: 123, + height: 321 + } + }) + ] + }) + }); + }); + + it('should work when called multiple times for the same auction', () => { + startParallel(); + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); + startParallel(); + expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); + }); + + it('should hide TIDs from buildPAAPIConfigs', () => { + config.setConfig({enableTIDs: false}); + startParallel(); + sinon.assert.calledWith( + spec.buildPAAPIConfigs, + sinon.match(bidRequests => bidRequests.every(req => req.auctionId == null)), + sinon.match(bidderRequest => bidderRequest.auctionId == null) + ); + }); + + it('should show TIDs when enabled', () => { + config.setConfig({enableTIDs: true}); + startParallel(); + sinon.assert.calledWith( + spec.buildPAAPIConfigs, + sinon.match(bidRequests => bidRequests.every(req => req.auctionId === 'aid')), + sinon.match(bidderRequest => bidderRequest.auctionId === 'aid') + ) + }) + + it('should respect requestedSize from adapter', () => { + mockConfig.requestedSize = {width: 1, height: 2}; + startParallel(); + sinon.assert.match(getPAAPIConfig().au, { + requestedSize: { + width: 123, + height: 321 + }, + componentAuctions: [sinon.match({ + requestedSize: { + width: 1, + height: 2 + } + })] + }) + }) + + it('should not accept multiple partial configs for the same bid/seller', () => { + builtCfg.push(builtCfg[0]) + startParallel(); + expect(getPAAPIConfig().au.componentAuctions.length).to.eql(1); + }); + it('should resolve top level config with auction signals', async () => { + startParallel(); + let config = getPAAPIConfig().au; + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, { + auctionSignals: { + prebid: {bidfloor: 1} + } + }) + }); + + describe('when adapter returns the rest of auction config', () => { + let configRemainder; + beforeEach(() => { + configRemainder = { + ...Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, {type: signal}])), + seller: 'mock.seller' + }; + }) + function returnRemainder() { + addPaapiConfigHook(sinon.stub(), bids[0], {config: configRemainder}); + } + it('should resolve component configs with values returned by adapters', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, configRemainder); + }); + + it('should pick first config that matches bidId/seller', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + returnRemainder(); + const expectedSignals = {...configRemainder}; + configRemainder = { + ...configRemainder, + auctionSignals: { + this: 'should be ignored' + } + } + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, expectedSignals); + }); + + describe('should default to values returned from buildPAAPIConfigs when interpretResponse does not return', () => { + beforeEach(() => { + ASYNC_SIGNALS.forEach(signal => mockConfig[signal] = {default: signal}) + }); + Object.entries({ + 'returns no matching config'() { + }, + 'does not include values in response'() { + configRemainder = {}; + returnRemainder(); + } + }).forEach(([t, postResponse]) => { + it(t, async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + postResponse(); + endAuction(); + config = await resolveConfig(config); + sinon.assert.match(config, mockConfig); + }); + }); + }); + + it('should resolve to undefined when no value is available', async () => { + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + delete configRemainder.sellerSignals; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + expect(config.sellerSignals).to.be.undefined; + }); + + [ + { + start: {t: 'scalar', value: 'str'}, + end: {t: 'array', value: ['abc']}, + should: {t: 'array', value: ['abc']} + }, + { + start: {t: 'object', value: {a: 'b'}}, + end: {t: 'scalar', value: 'abc'}, + should: {t: 'scalar', value: 'abc'} + }, + { + start: {t: 'object', value: {outer: {inner: 'val'}}}, + end: {t: 'object', value: {outer: {other: 'val'}}}, + should: {t: 'merge', value: {outer: {inner: 'val', other: 'val'}}} + } + ].forEach(({start, end, should}) => { + it(`when buildPAAPIConfigs returns ${start.t}, interpretResponse return ${end.t}, promise should resolve to ${should.t}`, async () => { + mockConfig.sellerSignals = start.value + startParallel(); + let config = getPAAPIConfig().au.componentAuctions[0]; + configRemainder.sellerSignals = end.value; + returnRemainder(); + endAuction(); + config = await resolveConfig(config); + expect(config.sellerSignals).to.eql(should.value); + }) + }) + + it('should make extra configs available', async () => { + startParallel(); + returnRemainder(); + configRemainder = {...configRemainder, seller: 'other.seller'}; + returnRemainder(); + endAuction(); + let configs = getPAAPIConfig().au.componentAuctions; + configs = [await resolveConfig(configs[0]), configs[1]]; + expect(configs.map(cfg => cfg.seller)).to.eql(['mock.seller', 'other.seller']); + }); + + describe('submodule\'s onAuctionConfig', () => { + let onAuctionConfig; + beforeEach(() => { + onAuctionConfig = sinon.stub(); + registerSubmodule({onAuctionConfig}) + }); + + Object.entries({ + 'parallel=true, some configs deferred': { + setup() { + config.mergeConfig({paapi: {parallel: true}}) + }, + delayed: false, + }, + 'parallel=true, no deferred configs': { + setup() { + config.mergeConfig({paapi: {parallel: true}}); + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => []); + }, + delayed: true + }, + 'parallel=false, some configs deferred': { + setup() { + config.mergeConfig({paapi: {parallel: false}}) + }, + delayed: true + } + }).forEach(([t, {setup, delayed}]) => { + describe(`when ${t}`, () => { + beforeEach(() => { + mockAuction.requestsDone = Promise.resolve(); + setup(); + }); + + function expectInvoked(shouldBeInvoked) { + if (shouldBeInvoked) { + sinon.assert.calledWith(onAuctionConfig, 'aid', sinon.match(arg => arg.au.componentAuctions[0].seller === 'mock.seller')); + } else { + sinon.assert.notCalled(onAuctionConfig); + } + } + + it(`should invoke onAuctionConfig when ${delayed ? 'auction ends' : 'auction requests have started'}`, async () => { + startParallel(); + await mockAuction.requestsDone; + expectInvoked(!delayed); + onAuctionConfig.reset(); + returnRemainder(); + endAuction(); + expectInvoked(delayed); + }) + }) + }) + }) + }); + }); + describe('when buildPAAPIConfigs returns igb', () => { + let builtCfg, igb, auctionConfig; + beforeEach(() => { + igb = {origin: 'mock.buyer'} + builtCfg = [{bidId: 'bidId', igb}]; + spec.buildPAAPIConfigs = sinon.stub().callsFake(() => builtCfg); + auctionConfig = { + seller: 'mock.seller', + decisionLogicUrl: 'mock.seller/decisionLogic' + } + config.mergeConfig({ + paapi: { + componentSeller: { + auctionConfig + } + } + }) + bidderRequest.paapi.componentSeller = true; + }); + Object.entries({ + 'componentSeller not configured'() { + bidderRequest.paapi.componentSeller = false; + }, + 'buildPAAPIconfig returns nothing'() { + builtCfg = [] + }, + 'returned igb is not valid'() { + builtCfg = [{bidId: 'bidId', igb: {}}]; + } + }).forEach(([t, setup]) => { + it(`should have no effect when ${t}`, () => { + setup(); + startParallel(); + expect(getPAAPIConfig()).to.eql({}); + }) + }) + + describe('when component seller is set up', () => { + it('should generate a deferred auctionConfig', () => { + startParallel(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + ...auctionConfig, + interestGroupBuyers: ['mock.buyer'], + }) + }); + + it('should use signal values from componentSeller.auctionConfig', async () => { + auctionConfig.auctionSignals = {test: 'signal'}; + config.mergeConfig({ + paapi: {componentSeller: {auctionConfig}} + }) + startParallel(); + endAuction(); + const cfg = await resolveConfig(getPAAPIConfig().au.componentAuctions[0]); + sinon.assert.match(cfg.auctionSignals, auctionConfig.auctionSignals); + }) + + it('should collate buyers', () => { + startParallel(); + startParallel(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + interestGroupBuyers: ['mock.buyer'] + }); + }); + + function returnIgb(igb) { + addPaapiConfigHook(sinon.stub(), bids[0], {igb}); + } + + it('should resolve to values from interpretResponse as well as buildPAAPIConfigs', async () => { + igb.cur = 'cur'; + igb.pbs = {over: 'ridden'} + startParallel(); + let cfg = getPAAPIConfig().au.componentAuctions[0]; + returnIgb({ + origin: 'mock.buyer', + pbs: {some: 'signal'} + }); + endAuction(); + cfg = await resolveConfig(cfg); + sinon.assert.match(cfg, { + perBuyerSignals: { + [igb.origin]: {some: 'signal'}, + }, + perBuyerCurrencies: { + [igb.origin]: 'cur' + } + }) + }); + + it('should not overwrite config once resolved', () => { + startParallel(); + returnIgb({ + origin: 'mock.buyer', + }); + endAuction(); + const cfg = getPAAPIConfig().au; + sinon.assert.match(cfg, Object.fromEntries(ASYNC_SIGNALS.map(signal => [signal, sinon.match(arg => arg instanceof Promise)]))) + }) + + it('can resolve multiple igbs', async () => { + igb.cur = 'cur1'; + startParallel(); + spec.code = 'other'; + igb.origin = 'other.buyer' + igb.cur = 'cur2' + startParallel(); + let cfg = getPAAPIConfig().au.componentAuctions[0]; + returnIgb({ + origin: 'mock.buyer', + pbs: {signal: 1} + }); + returnIgb({ + origin: 'other.buyer', + pbs: {signal: 2} + }); + endAuction(); + cfg = await resolveConfig(cfg); + sinon.assert.match(cfg, { + perBuyerSignals: { + 'mock.buyer': {signal: 1}, + 'other.buyer': {signal: 2} + }, + perBuyerCurrencies: { + 'mock.buyer': 'cur1', + 'other.buyer': 'cur2' + } + }) + }) + + function startMultiple() { + startParallel(); + spec.code = 'other'; + igb.origin = 'other.buyer' + startParallel(); + } + + describe('when using separateAuctions=false', () => { + beforeEach(() => { + config.mergeConfig({ + paapi: { + componentSeller: { + separateAuctions: false + } + } + }) + }); + + it('should merge igb from different specs into a single auction config', () => { + startMultiple(); + sinon.assert.match(getPAAPIConfig().au.componentAuctions[0], { + interestGroupBuyers: ['mock.buyer', 'other.buyer'] + }); + }); + }) + + describe('when using separateAuctions=true', () => { + beforeEach(() => { + config.mergeConfig({ + paapi: { + componentSeller: { + separateAuctions: true + } + } + }) + }); + it('should generate an auction config for each bidder', () => { + startMultiple(); + const components = getPAAPIConfig().au.componentAuctions; + sinon.assert.match(components[0], { + interestGroupBuyers: ['mock.buyer'] + }) + sinon.assert.match(components[1], { + interestGroupBuyers: ['other.buyer'] + }) + }) + }) + }) + }) + }); + }); + + describe('ortb processors for fledge', () => { + it('imp.ext.ae should be removed if fledge is not enabled', () => { + const imp = {ext: {ae: 1, igs: {}}}; + setImpExtAe(imp, {}, {bidderRequest: {}}); + expect(imp.ext.ae).to.not.exist; + expect(imp.ext.igs).to.not.exist; + }); + it('imp.ext.ae should be left intact if fledge is enabled', () => { + const imp = {ext: {ae: 2, igs: {biddable: 0}}}; + setImpExtAe(imp, {}, {bidderRequest: {paapi: {enabled: true}}}); + expect(imp.ext).to.eql({ + ae: 2, + igs: { + biddable: 0 + } + }); + }); + + describe('response parsing', () => { + function generateImpCtx(fledgeFlags) { + return Object.fromEntries(Object.entries(fledgeFlags).map(([impid, fledgeEnabled]) => [impid, {imp: {ext: {ae: fledgeEnabled}}}])); + } + + function extractResult(type, ctx) { + return Object.fromEntries( Object.entries(ctx) - .map(([impid, ctx]) => [impid, ctx.fledgeConfigs?.map(cfg => cfg.config.id)]) + .map(([impid, ctx]) => [impid, ctx.paapiConfigs?.map(cfg => cfg[type].id)]) .filter(([_, val]) => val != null) ); } - it('should collect fledge configs by imp', () => { - const ctx = { - impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) - }; - const resp = packageConfigs( - generateCfg('e1', 1, 2, 3) - .concat(generateCfg('e2', 4) - .concat(generateCfg('d1', 5, 6))) - ); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({ - e1: [1, 2, 3], - e2: [4], + Object.entries({ + 'parseExtPrebidFledge': { + parser: parseExtPrebidFledge, + responses: { + 'ext.prebid.fledge'(configs) { + return { + ext: { + prebid: { + fledge: { + auctionconfigs: configs + } + } + } + }; + }, + } + }, + 'parseExtIgi': { + parser: parseExtIgi, + responses: { + 'ext.igi.igs'(configs) { + return { + ext: { + igi: [{ + igs: configs + }] + } + }; + }, + 'ext.igi.igs with impid on igi'(configs) { + return { + ext: { + igi: configs.map(cfg => { + const impid = cfg.impid; + delete cfg.impid; + return { + impid, + igs: [cfg] + }; + }) + } + }; + }, + 'ext.igi.igs with conflicting impid'(configs) { + return { + ext: { + igi: [{ + impid: 'conflict', + igs: configs + }] + } + }; + } + } + } + }).forEach(([t, {parser, responses}]) => { + describe(t, () => { + Object.entries(responses).forEach(([t, packageConfigs]) => { + describe(`when response uses ${t}`, () => { + function generateCfg(impid, ...ids) { + return ids.map((id) => ({impid, config: {id}})); + } + + it('should collect auction configs by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = packageConfigs( + generateCfg('e1', 1, 2, 3) + .concat(generateCfg('e2', 4) + .concat(generateCfg('d1', 5, 6))) + ); + parser({}, resp, ctx); + expect(extractResult('config', ctx.impContext)).to.eql({ + e1: [1, 2, 3], + e2: [4], + }); + }); + it('should not choke if fledge config references unknown imp', () => { + const ctx = {impContext: generateImpCtx({i: 1})}; + const resp = packageConfigs(generateCfg('unknown', 1)); + parser({}, resp, ctx); + expect(extractResult('config', ctx.impContext)).to.eql({}); + }); + }); + }); }); }); - it('should not choke if fledge config references unknown imp', () => { - const ctx = {impContext: generateImpCtx({i: 1})}; - const resp = packageConfigs(generateCfg('unknown', 1)); - parseExtPrebidFledge({}, resp, ctx); - expect(extractResult(ctx.impContext)).to.eql({}); + + describe('response ext.igi.igb', () => { + it('should collect igb by imp', () => { + const ctx = { + impContext: generateImpCtx({e1: 1, e2: 1, d1: 0}) + }; + const resp = { + ext: { + igi: [ + { + impid: 'e1', + igb: [ + {id: 1}, + {id: 2} + ] + }, + { + impid: 'e2', + igb: [ + {id: 3} + ] + }, + { + impid: 'd1', + igb: [ + {id: 4} + ] + } + ] + } + }; + parseExtIgi({}, resp, ctx); + expect(extractResult('igb', ctx.impContext)).to.eql({ + e1: [1, 2], + e2: [3], + }); + }); }); }); - describe('setResponseFledgeConfigs', () => { - it('should set fledgeAuctionConfigs paired with their corresponding bid id', () => { + + describe('setResponsePaapiConfigs', () => { + it('should set paapi configs/igb paired with their corresponding bid id', () => { const ctx = { impContext: { 1: { bidRequest: {bidId: 'bid1'}, - fledgeConfigs: [{config: {id: 1}}, {config: {id: 2}}] + paapiConfigs: [{config: {id: 1}}, {config: {id: 2}}] }, 2: { bidRequest: {bidId: 'bid2'}, - fledgeConfigs: [{config: {id: 3}}] + paapiConfigs: [{config: {id: 3}}] }, 3: { bidRequest: {bidId: 'bid3'} + }, + 4: { + bidRequest: {bidId: 'bid1'}, + paapiConfigs: [{igb: {id: 4}}] } } }; const resp = {}; - setResponseFledgeConfigs(resp, {}, ctx); - expect(resp.fledgeAuctionConfigs).to.eql([ + setResponsePaapiConfigs(resp, {}, ctx); + expect(resp.paapi).to.eql([ {bidId: 'bid1', config: {id: 1}}, {bidId: 'bid1', config: {id: 2}}, {bidId: 'bid2', config: {id: 3}}, + {bidId: 'bid1', igb: {id: 4}} ]); }); - it('should not set fledgeAuctionConfigs if none exist', () => { + it('should not set paapi if no config or igb exists', () => { const resp = {}; - setResponseFledgeConfigs(resp, {}, { + setResponsePaapiConfigs(resp, {}, { impContext: { 1: { - fledgeConfigs: [] + paapiConfigs: [] }, 2: {} } diff --git a/test/spec/modules/parrableIdSystem_spec.js b/test/spec/modules/parrableIdSystem_spec.js deleted file mode 100644 index 55287e0bfec..00000000000 --- a/test/spec/modules/parrableIdSystem_spec.js +++ /dev/null @@ -1,762 +0,0 @@ -import { expect } from 'chai'; -import {find} from 'src/polyfill.js'; -import { config } from 'src/config.js'; -import * as utils from 'src/utils.js'; -import { newStorageManager } from 'src/storageManager.js'; -import { getRefererInfo } from 'src/refererDetection.js'; -import { uspDataHandler } from 'src/adapterManager.js'; -import { init, requestBidsHook, setSubmoduleRegistry } from 'modules/userId/index.js'; -import { parrableIdSubmodule } from 'modules/parrableIdSystem.js'; -import { server } from 'test/mocks/xhr.js'; -import {mockGdprConsent} from '../../helpers/consentData.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: { - partners: 'parrable_test_partner_123,parrable_test_partner_456' - } -}; -const RESPONSE_HEADERS = { 'Content-Type': 'application/json' }; - -function getConfigMock() { - return { - userSync: { - syncDelay: 0, - userIds: [P_CONFIG_MOCK] - } - } -} - -function getAdUnitMock(code = 'adUnit-code') { - return { - code, - mediaTypes: {banner: {}, native: {}}, - sizes: [ - [300, 200], - [300, 600] - ], - bids: [{ - bidder: 'sampleBidder', - params: { placementId: 'banner-only-bidder' } - }] - }; -} - -function serializeParrableId(parrableId) { - let str = ''; - if (parrableId.eid) { - str += 'eid:' + parrableId.eid; - } - if (parrableId.ibaOptout) { - str += ',ibaOptout:1'; - } - 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; -} - -function writeParrableCookie(parrableId) { - let cookieValue = encodeURIComponent(serializeParrableId(parrableId)); - storage.setCookie( - P_COOKIE_NAME, - cookieValue, - (new Date(Date.now() + EXPIRE_COOKIE_TIME).toUTCString()), - 'lax' - ); -} - -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() { - after(() => { - // reset ID system to avoid delayed callbacks in other tests - config.resetConfig(); - init(config); - }); - - 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 }); - }); - - afterEach(function() { - removeParrableCookie(); - logErrorStub.restore(); - }) - - it('creates xhr to Parrable that synchronizes the ID', function() { - let getIdResult = parrableIdSubmodule.getId(P_CONFIG_MOCK); - - getIdResult.callback(callbackSpy); - - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - - 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().page, - 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) - ); - }); - - 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); - }); - - 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); - - getIdResult.callback(callbackSpy); - - let request = server.requests[0]; - let queryParams = utils.parseQS(request.url.split('?')[1]); - expect(queryParams.data).to.equal(urlSafeBase64EncodedData); - btoaStub.restore(); - }); - - 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; - }); - }); - - 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(); - }); - }); - - 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('third party cookie support', function () { - let logErrorStub; - let callbackSpy = sinon.spy(); - - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - callbackSpy.resetHistory(); - removeParrableCookie(); - }); - - afterEach(function() { - logErrorStub.restore(); - }); - - describe('when getting tpcSupport from XHR response', function () { - let request; - let dateNowStub; - const dateNowMock = Date.now(); - const tpcSupportTtl = 1; - - before(() => { - dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); - }); - - after(() => { - dateNowStub.restore(); - }); - - it('should set tpcSupport: true and tpcUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, tpcSupport: true, tpcSupportTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID + ',tpc:1,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) - ); - }); - - it('should set tpcSupport: false and tpcUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, tpcSupport: false, tpcSupportTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID + ',tpc:0,tpcUntil:' + Math.floor((dateNowMock / 1000) + tpcSupportTtl)) - ); - }); - - it('should not set tpcSupport in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent('eid:' + P_XHR_EID) - ); - }); - }); - }); - - describe('request-filter status', function () { - let logErrorStub; - let callbackSpy = sinon.spy(); - - beforeEach(function() { - logErrorStub = sinon.stub(utils, 'logError'); - }); - - afterEach(function () { - callbackSpy.resetHistory(); - removeParrableCookie(); - }); - - afterEach(function() { - logErrorStub.restore(); - }); - - describe('when getting filterTtl from XHR response', function () { - let request; - let dateNowStub; - const dateNowMock = Date.now(); - const filterTtl = 1000; - - before(() => { - dateNowStub = sinon.stub(Date, 'now').returns(dateNowMock); - }); - - after(() => { - dateNowStub.restore(); - }); - - it('should set filteredUntil in the cookie', function () { - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - request.respond( - 200, - RESPONSE_HEADERS, - JSON.stringify({ eid: P_XHR_EID, filterTtl }) - ); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent( - 'eid:' + P_XHR_EID + - ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + - ',filterHits:0') - ); - }); - - it('should increment filterHits in the cookie', function () { - writeParrableCookie({ - eid: P_XHR_EID, - filteredUntil: Math.floor((dateNowMock / 1000) + filterTtl), - filterHits: 0 - }); - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - - expect(storage.getCookie(P_COOKIE_NAME)).to.equal( - encodeURIComponent( - 'eid:' + P_XHR_EID + - ',filteredUntil:' + Math.floor((dateNowMock / 1000) + filterTtl) + - ',filterHits:1') - ); - }); - - it('should send filterHits in the XHR', function () { - const filterHits = 1; - writeParrableCookie({ - eid: P_XHR_EID, - filteredUntil: Math.floor(dateNowMock / 1000), - filterHits - }); - let { callback } = parrableIdSubmodule.getId(P_CONFIG_MOCK); - callback(callbackSpy); - request = server.requests[0]; - - let queryParams = utils.parseQS(request.url.split('?')[1]); - let data = JSON.parse(atob(decodeBase64UrlSafe(queryParams.data))); - - expect(data.filterHits).to.equal(filterHits); - }); - }); - }); - }); - - describe('parrableIdSystem.decode()', function() { - it('provides the Parrable ID (EID) from a stored object', function() { - let eid = '01.123.4567890'; - let parrableId = { - eid, - ibaOptout: true - }; - - expect(parrableIdSubmodule.decode(parrableId)).to.deep.equal({ - 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; - let sandbox; - - beforeEach(function() { - sandbox = sinon.sandbox.create(); - mockGdprConsent(sandbox); - adUnits = [getAdUnitMock()]; - writeParrableCookie({ eid: P_COOKIE_EID, ibaOptout: true }); - init(config); - setSubmoduleRegistry([parrableIdSubmodule]); - }); - - afterEach(function() { - removeParrableCookie(); - storage.setCookie(P_COOKIE_NAME, '', EXPIRED_COOKIE_DATE); - sandbox.restore(); - }); - - it('when a stored Parrable ID exists it is added to bids', function(done) { - config.setConfig(getConfigMock()); - requestBidsHook(function() { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - 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 }); - config.setConfig(getConfigMock()); - - 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/permutiveIdentityManagerIdSystem_spec.js b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js new file mode 100644 index 00000000000..96c581844c1 --- /dev/null +++ b/test/spec/modules/permutiveIdentityManagerIdSystem_spec.js @@ -0,0 +1,126 @@ +import { permutiveIdentityManagerIdSubmodule, storage } from 'modules/permutiveIdentityManagerIdSystem' +import { deepSetValue } from 'src/utils.js' + +const STORAGE_KEY = 'permutive-prebid-id' + +describe('permutiveIdentityManagerIdSystem', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(STORAGE_KEY) + }) + + describe('decode', () => { + it('returns the input unchanged', () => { + const input = { + id5id: { + uid: '0', + ext: { + abTestingControlGroup: false, + linkType: 2, + pba: 'somepba' + } + } + } + const result = permutiveIdentityManagerIdSubmodule.decode(input) + expect(result).to.be.equal(input) + }) + }) + + describe('getId', () => { + it('returns relevant IDs from localStorage and does not return unexpected IDs', () => { + const data = getUserIdData() + storage.setDataInLocalStorage(STORAGE_KEY, JSON.stringify(data)) + const result = permutiveIdentityManagerIdSubmodule.getId({}) + const expected = { + 'id': { + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + } + } + expect(result).to.deep.equal(expected) + }) + + it('returns undefined if no relevant IDs are found in localStorage', () => { + storage.setDataInLocalStorage(STORAGE_KEY, '{}') + const result = permutiveIdentityManagerIdSubmodule.getId({}) + expect(result).to.be.undefined + }) + + it('will optionally wait for Permutive SDK if no identities are in local storage already', async () => { + const cleanup = setWindowPermutive() + const result = permutiveIdentityManagerIdSubmodule.getId({params: {ajaxTimeout: 50}}) + expect(result).not.to.be.undefined + expect(result.id).to.be.undefined + expect(result.callback).not.to.be.undefined + const expected = { + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + } + const r = await new Promise(result.callback) + expect(r).to.deep.equal(expected) + cleanup() + }) + }) +}) + +const setWindowPermutive = () => { + // Read from Permutive + const backup = window.permutive + + deepSetValue(window, 'permutive.ready', (f) => { + setTimeout(() => f(), 5) + }) + + deepSetValue(window, 'permutive.addons.identity_manager.prebid.onReady', (f) => { + setTimeout(() => f(sdkUserIdData()), 5) + }) + + // Cleanup + return () => window.permutive = backup +} + +const sdkUserIdData = () => ({ + 'id5id': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + }, +}) + +const getUserIdData = () => ({ + 'providers': { + 'id5id': { + 'userId': { + 'uid': '0', + 'linkType': 0, + 'ext': { + 'abTestingControlGroup': false, + 'linkType': 0, + 'pba': 'EVqgf9vY0fSrsrqJZMOm+Q==' + } + } + }, + 'fooid': { + 'userId': { + 'id': '1' + } + } + } +}) diff --git a/test/spec/modules/permutiveRtdProvider_spec.js b/test/spec/modules/permutiveRtdProvider_spec.js index 73218fee7b9..51fbba7e936 100644 --- a/test/spec/modules/permutiveRtdProvider_spec.js +++ b/test/spec/modules/permutiveRtdProvider_spec.js @@ -484,7 +484,9 @@ describe('permutiveRtdProvider', function () { _psegs: [], _ppam: [], _pcrprs: [], - _pssps: { ssps: [], cohorts: [] } + _pindexs: [], + _pssps: { ssps: [], cohorts: [] }, + _ppsts: {}, }) setBidderRtb(bidderConfig, moduleConfig, segmentsData) @@ -568,6 +570,7 @@ describe('permutiveRtdProvider', function () { const data = transformedTargeting() expect(getSegments(250)).to.deep.equal(data) }) + it('should enforce max segments', function () { const max = 1 const segments = getSegments(max) @@ -584,6 +587,26 @@ describe('permutiveRtdProvider', function () { } } }) + + it('should coerce numbers to strings', function () { + setLocalStorage({ _prubicons: [1, 2, 3], _pssps: { ssps: ['foo', 'bar'], cohorts: [4, 5, 6] } }) + + const segments = getSegments(200) + + expect(segments.rubicon).to.deep.equal(['1', '2', '3']) + expect(segments.ssp.ssps).to.deep.equal(['foo', 'bar']) + expect(segments.ssp.cohorts).to.deep.equal(['4', '5', '6']) + }) + + it('should return empty values on unexpected format', function () { + setLocalStorage({ _prubicons: 'a string instead?', _pssps: 123 }) + + const segments = getSegments(200) + + expect(segments.rubicon).to.deep.equal([]) + expect(segments.ssp.ssps).to.deep.equal([]) + expect(segments.ssp.cohorts).to.deep.equal([]) + }) }) describe('Existing key-value targeting', function () { @@ -703,14 +726,25 @@ function getConfig () { } function transformedTargeting (data = getTargetingData()) { + const topics = (() => { + const topics = {} + for (const topic in data._ppsts) { + topics[topic] = data._ppsts[topic].map(String) + } + return topics + })() + return { - ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)], - appnexus: data._papns, - ix: data._pindexs, - rubicon: data._prubicons, - gam: data._pdfps, - ssp: data._pssps, - topics: data._ppsts, + ac: [...data._pcrprs, ...data._ppam, ...data._psegs.filter(seg => seg >= 1000000)].map(String), + appnexus: data._papns.map(String), + ix: data._pindexs.map(String), + rubicon: data._prubicons.map(String), + gam: data._pdfps.map(String), + ssp: { + ssps: data._pssps.ssps.map(String), + cohorts: data._pssps.cohorts.map(String) + }, + topics, } } diff --git a/test/spec/modules/pgamsspBidAdapter_spec.js b/test/spec/modules/pgamsspBidAdapter_spec.js index 281a012fb7a..bdb837b70a4 100644 --- a/test/spec/modules/pgamsspBidAdapter_spec.js +++ b/test/spec/modules/pgamsspBidAdapter_spec.js @@ -3,9 +3,19 @@ import { spec } from '../../../modules/pgamsspBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'pgamssp' +const bidder = 'pgamssp'; describe('PGAMBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('PGAMBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -74,10 +87,19 @@ describe('PGAMBidAdapter', function () { const bidderRequest = { uspConsent: '1---', gdprConsent: { - consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -114,6 +136,7 @@ describe('PGAMBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -147,7 +170,56 @@ describe('PGAMBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); - expect(placement.eids).to.exist.and.to.be.an('array'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -174,6 +246,8 @@ describe('PGAMBidAdapter', function () { let data = serverRequest.data; expect(data.gdpr).to.exist; expect(data.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; @@ -189,12 +263,6 @@ describe('PGAMBidAdapter', function () { 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('gpp consent', function () { diff --git a/test/spec/modules/pirIdSystem_spec.js b/test/spec/modules/pirIdSystem_spec.js deleted file mode 100644 index 5acc5a5eb9c..00000000000 --- a/test/spec/modules/pirIdSystem_spec.js +++ /dev/null @@ -1,77 +0,0 @@ -import { pirIdSubmodule, storage, readId } from 'modules/pirIdSystem.js'; -import sinon from 'sinon'; - -describe('pirIdSystem', () => { - let sandbox; - let getCookieStub; - let getDataFromLocalStorageStub; - - beforeEach(() => { - sandbox = sinon.createSandbox(); - getCookieStub = sandbox.stub(storage, 'getCookie'); - getDataFromLocalStorageStub = sandbox.stub(storage, 'getDataFromLocalStorage'); - }); - - afterEach(() => { - sandbox.restore(); - }); - - describe('getId', () => { - it('should return an object with id when pirIdToken is found', () => { - getDataFromLocalStorageStub.returns('testToken'); - getCookieStub.returns('testToken'); - - const result = pirIdSubmodule.getId(); - - expect(result).to.deep.equal({ id: 'testToken' }); - }); - - it('should return undefined when pirIdToken is not found', () => { - const result = pirIdSubmodule.getId(); - - expect(result).to.be.undefined; - }); - }); - - describe('decode', () => { - it('should return an object with pirId when value is a string', () => { - const result = pirIdSubmodule.decode('testId'); - - expect(result).to.deep.equal({ pirId: 'testId' }); - }); - - it('should return undefined when value is not a string', () => { - const result = pirIdSubmodule.decode({}); - - expect(result).to.be.undefined; - }); - }); - - describe('readId', () => { - it('should return data from local storage when it exists', () => { - getDataFromLocalStorageStub.returns('local_storage_data'); - - const result = readId(); - - expect(result).to.equal('local_storage_data'); - }); - - it('should return data from cookie when local storage data does not exist', () => { - getDataFromLocalStorageStub.returns(null); - getCookieStub.returns('cookie_data'); - - const result = readId(); - - expect(result).to.equal('cookie_data'); - }); - - it('should return null when neither local storage data nor cookie data exists', () => { - getDataFromLocalStorageStub.returns(null); - getCookieStub.returns(null); - - const result = readId(); - - expect(result).to.be.null; - }); - }); -}); diff --git a/test/spec/modules/pixfutureBidAdapter_spec.js b/test/spec/modules/pixfutureBidAdapter_spec.js index a236478c9b4..bdf40fbb06b 100644 --- a/test/spec/modules/pixfutureBidAdapter_spec.js +++ b/test/spec/modules/pixfutureBidAdapter_spec.js @@ -43,12 +43,12 @@ describe('PixFutureAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'pix_id': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/playdigoBidAdapter_spec.js b/test/spec/modules/playdigoBidAdapter_spec.js index 80fc3c96e81..591892beb8c 100644 --- a/test/spec/modules/playdigoBidAdapter_spec.js +++ b/test/spec/modules/playdigoBidAdapter_spec.js @@ -6,6 +6,16 @@ import { getUniqueIdentifierStr } from '../../../src/utils.js'; const bidder = 'playdigo' describe('PlaydigoBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PlaydigoBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PlaydigoBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -54,7 +66,8 @@ describe('PlaydigoBidAdapter', function () { }, params: { placementId: 'testNative' - } + }, + userIdAsEids } ]; @@ -78,8 +91,17 @@ describe('PlaydigoBidAdapter', function () { vendorData: {} }, refererInfo: { - referer: 'https://test.com' - } + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 }; describe('isBidRequestValid', function () { @@ -105,11 +127,16 @@ describe('PlaydigoBidAdapter', function () { expect(serverRequest.method).to.equal('POST'); }); + it('Returns valid URL', function () { + expect(serverRequest.url).to.equal('https://server.playdigo.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', + 'device', 'language', 'secure', 'host', @@ -143,6 +170,7 @@ describe('PlaydigoBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -175,9 +203,10 @@ describe('PlaydigoBidAdapter', function () { }, params: { endpointId: 'testBanner', - } + }, + userIdAsEids } - ] + ]; let serverRequest = spec.buildRequests(bids, bidderRequest); @@ -190,6 +219,7 @@ describe('PlaydigoBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); diff --git a/test/spec/modules/prebidServerBidAdapter_spec.js b/test/spec/modules/prebidServerBidAdapter_spec.js index 45743730221..2dd80537f1f 100644 --- a/test/spec/modules/prebidServerBidAdapter_spec.js +++ b/test/spec/modules/prebidServerBidAdapter_spec.js @@ -21,17 +21,17 @@ import 'modules/currency.js'; // adServerCurrency test import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; -import 'modules/fledgeForGpt.js'; +import 'modules/paapi.js'; import * as redactor from 'src/activities/redactor.js'; import * as activityRules from 'src/activities/rules.js'; import {hook} from '../../../src/hook.js'; import {decorateAdUnitsWithNativeParams} from '../../../src/native.js'; import {auctionManager} from '../../../src/auctionManager.js'; import {stubAuctionIndex} from '../../helpers/indexStub.js'; -import {addComponentAuction, registerBidder} from 'src/adapters/bidderFactory.js'; +import {addPaapiConfig, registerBidder} from 'src/adapters/bidderFactory.js'; import {getGlobal} from '../../../src/prebidGlobal.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepSetValue} from '../../../src/utils.js'; @@ -42,7 +42,6 @@ let CONFIG = { accountId: '1', enabled: true, bidders: ['appnexus'], - timeout: 1000, cacheMarkup: 2, endpoint: { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction', @@ -763,12 +762,82 @@ describe('S2S Adapter', function () { }); }) - it('should set tmax to s2sConfig.timeout', () => { - const cfg = {...CONFIG, timeout: 123}; - config.setConfig({s2sConfig: cfg}); - adapter.callBids({...REQUEST, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + it('should set tmaxmax correctly when publisher has specified it', () => { + const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) + + // publisher has specified a tmaxmax in their setup + const ortb2Fragments = { + global: { + ext: { + tmaxmax: 4242 + } + } + }; + const s2sCfg = {...REQUEST, cfg} + const payloadWithFragments = { ...s2sCfg, ortb2Fragments }; + + adapter.callBids(payloadWithFragments, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + + expect(req.ext.tmaxmax).to.eql(4242); + }); + + it('should set tmaxmax correctly when publisher has not specified it', () => { + const cfg = {...CONFIG}; + config.setConfig({s2sConfig: cfg}) + + // publisher has not specified a tmaxmax in their setup - so we should be + // falling back to requestBidsTimeout + const ortb2Fragments = {}; + const s2sCfg = {...REQUEST, cfg}; + const requestBidsTimeout = 808; + const payloadWithFragments = { ...s2sCfg, ortb2Fragments, requestBidsTimeout }; + + adapter.callBids(payloadWithFragments, BID_REQUESTS, addBidResponse, done, ajax); const req = JSON.parse(server.requests[0].requestBody); - expect(req.tmax).to.eql(123); + + expect(req.ext.tmaxmax).to.eql(808); + }); + + describe('default tmax', () => { + [null, 3000].forEach(maxTimeout => { + describe(`when maxTimeout is ${maxTimeout}`, () => { + let cfg; + + beforeEach(() => { + cfg = {accountId: '1', endpoint: 'mock-endpoint', maxTimeout}; + config.setConfig({s2sConfig: cfg}); + maxTimeout = maxTimeout ?? s2sDefaultConfig.maxTimeout + }); + + it('should cap tmax to maxTimeout', () => { + adapter.callBids({...REQUEST, requestBidsTimeout: maxTimeout * 2, s2sConfig: cfg}, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.tmax).to.eql(maxTimeout); + }); + + it('should be set to 0.75 * requestTimeout, if lower than maxTimeout', () => { + adapter.callBids({...REQUEST, requestBidsTimeout: maxTimeout / 2}, BID_REQUESTS, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.tmax).to.eql(maxTimeout / 2 * 0.75); + }) + }) + }) + }) + + it('should set customHeaders correctly when publisher has provided it', () => { + let configWithCustomHeaders = utils.deepClone(CONFIG); + configWithCustomHeaders.customHeaders = { customHeader1: 'customHeader1Value' }; + config.setConfig({ s2sConfig: configWithCustomHeaders }); + + let reqWithNewConfig = utils.deepClone(REQUEST); + reqWithNewConfig.s2sConfig = configWithCustomHeaders; + + adapter.callBids(reqWithNewConfig, BID_REQUESTS, addBidResponse, done, ajax); + const reqHeaders = server.requests[0].requestHeaders + expect(reqHeaders.customHeader1).to.exist; + expect(reqHeaders.customHeader1).to.equal('customHeader1Value'); }); it('should block request if config did not define p1Consent URL in endpoint object config', function () { @@ -836,22 +905,6 @@ describe('S2S Adapter', function () { 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'; @@ -865,7 +918,6 @@ describe('S2S Adapter', function () { 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; @@ -1021,17 +1073,23 @@ describe('S2S Adapter', function () { it('adds device and app objects to request', function () { const _config = { s2sConfig: CONFIG, - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sreq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + } + } + }, BID_REQUESTS) + adapter.callBids(s2sreq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1045,20 +1103,25 @@ describe('S2S Adapter', function () { p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' } }); - const _config = { s2sConfig: s2sConfig, - device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + device: { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC' }, + app: { bundle: 'com.test.app' }, + } + } + }, BID_REQUESTS) + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { ifa: '6D92078A-8246-4BA4-AE5B-76104861E7DC', - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) sinon.assert.match(requestBid.app, { bundle: 'com.test.app', @@ -1412,20 +1475,14 @@ describe('S2S Adapter', function () { it('adds device.w and device.h even if the config lacks a device object', function () { const _config = { s2sConfig: CONFIG, - app: { bundle: 'com.test.app' }, }; - config.setConfig(_config); adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); sinon.assert.match(requestBid.device, { - w: window.innerWidth, - h: window.innerHeight + w: window.screen.width, + h: window.screen.height, }) - sinon.assert.match(requestBid.app, { - bundle: 'com.test.app', - publisher: { 'id': '1' } - }); expect(requestBid.imp[0].native.ver).to.equal('1.2'); }); @@ -1509,19 +1566,26 @@ describe('S2S Adapter', function () { it('adds site if app is not present', function () { const _config = { s2sConfig: CONFIG, - site: { - publisher: { - id: '1234', - domain: 'test.com' - }, - content: { - language: 'en' - } - } }; config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); expect(requestBid.site.publisher).to.exist.and.to.be.a('object'); @@ -1546,23 +1610,31 @@ describe('S2S Adapter', function () { it('site should not be present when app is present', function () { const _config = { s2sConfig: CONFIG, - app: { bundle: 'com.test.app' }, - site: { - publisher: { - id: '1234', - domain: 'test.com' - }, - content: { - language: 'en' - } - } }; config.setConfig(_config); - adapter.callBids(addFpdEnrichmentsToS2SRequest(REQUEST, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...REQUEST, + ortb2Fragments: { + global: { + app: { bundle: 'com.test.app' }, + site: { + publisher: { + id: '1234', + domain: 'test.com' + }, + content: { + language: 'en' + } + } + } + } + }, BID_REQUESTS) + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.not.exist; - expect(requestBid.app).to.exist.and.to.be.a('object'); + expect(requestBid.app.bundle).to.eql('com.test.app'); }); it('adds appnexus aliases to request', function () { @@ -1726,39 +1798,6 @@ describe('S2S Adapter', function () { }); }); - it('converts appnexus params to expected format for PBS', function () { - const s2sConfig = Object.assign({}, CONFIG, { - endpoint: { - p1Consent: 'https://prebid.adnxs.com/pbs/v1/openrtb2/auction' - } - }); - config.setConfig({ s2sConfig: s2sConfig }); - - Object.assign(BID_REQUESTS[0].bids[0].params, { - usePaymentRule: true, - keywords: { - foo: ['bar', 'baz'], - fizz: ['buzz'] - } - }) - - adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); - const requestBid = JSON.parse(server.requests[0].requestBody); - - const requestParams = requestBid.imp[0].ext.prebid.bidder; - expect(requestParams.appnexus).to.exist; - expect(requestParams.appnexus.placement_id).to.exist.and.to.equal(10433394); - expect(requestParams.appnexus.use_pmt_rule).to.exist.and.to.be.true; - expect(requestParams.appnexus.member).to.exist; - expect(requestParams.appnexus.keywords).to.exist.and.to.deep.equal([{ - key: 'foo', - value: ['bar', 'baz'] - }, { - key: 'fizz', - value: ['buzz'] - }]); - }); - describe('cookie sync', () => { let s2sConfig, bidderReqs; @@ -1976,15 +2015,21 @@ describe('S2S Adapter', function () { it('and overrides publisher and page', function () { config.setConfig({ s2sConfig: s2sConfig, - site: { - domain: 'nytimes.com', - page: 'http://www.nytimes.com', - publisher: { id: '2' } - }, - device: device }); - - adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments: { + global: { + site: { + domain: 'nytimes.com', + page: 'http://www.nytimes.com', + publisher: { id: '2' } + }, + device, + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -1997,13 +2042,19 @@ describe('S2S Adapter', function () { it('and merges domain and page with the config site value', function () { config.setConfig({ s2sConfig: s2sConfig, - site: { - foo: 'bar' - }, - device: device }); - - adapter.callBids(addFpdEnrichmentsToS2SRequest(s2sBidRequest, BID_REQUESTS), BID_REQUESTS, addBidResponse, done, ajax); + const s2sReq = addFpdEnrichmentsToS2SRequest({ + ...s2sBidRequest, + ortb2Fragments: { + global: { + site: { + foo: 'bar' + }, + device: device + } + } + }, BID_REQUESTS); + adapter.callBids(s2sReq, BID_REQUESTS, addBidResponse, done, ajax); const requestBid = JSON.parse(server.requests[0].requestBody); expect(requestBid.site).to.exist.and.to.be.a('object'); @@ -2014,34 +2065,36 @@ describe('S2S Adapter', function () { }); }); - it('when userId is defined on bids, it\'s properties should be copied to user.ext.tpid properties', function () { - let consentConfig = { s2sConfig: CONFIG }; - config.setConfig(consentConfig); - - let userIdBidRequest = utils.deepClone(BID_REQUESTS); - userIdBidRequest[0].bids[0].userId = { - criteoId: '44VmRDeUE3ZGJ5MzRkRVJHU3BIUlJ6TlFPQUFU', - tdid: 'abc123', - pubcid: '1234', - parrableId: { eid: '01.1563917337.test-eid' }, - lipb: { - lipbid: 'li-xyz', - segments: ['segA', 'segB'] - }, - idl_env: '0000-1111-2222-3333', - id5id: { - uid: '11111', - ext: { - linkType: 'some-link-type' + it('should pass user.ext.eids from FPD', function () { + config.setConfig({s2sConfig: CONFIG}); + const req = { + ...REQUEST, + ortb2Fragments: { + global: { + user: { + ext: { + eids: [{id: 1}, {id: 2}] + } + } + }, + bidder: { + appnexus: { + user: { + ext: { + eids: [{id: 3}] + } + } + } } } - }; - userIdBidRequest[0].bids[0].userIdAsEids = [{id: 1}, {id: 2}]; - - adapter.callBids(REQUEST, userIdBidRequest, addBidResponse, done, ajax); - let requestBid = JSON.parse(server.requests[0].requestBody); - expect(typeof requestBid.user.ext.eids).is.equal('object'); - expect(requestBid.user.ext.eids).to.eql([{id: 1}, {id: 2}]); + } + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + const payload = JSON.parse(server.requests[0].requestBody); + expect(payload.user.ext.eids).to.eql([{id: 1}, {id: 2}]); + expect(payload.ext.prebid.bidderconfig).to.eql([{ + bidders: ['appnexus'], + config: {ortb2: {user: {ext: {eids: [{id: 3}]}}}} + }]); }); it('when config \'currency.adServerCurrency\' value is a string: ORTB has property \'cur\' value set to a single item array', function () { @@ -2377,23 +2430,28 @@ describe('S2S Adapter', function () { ]); }); - it('should "promote" the most reused bidder schain to source.ext.schain', () => { - const bidderReqs = [ - {...deepClone(BID_REQUESTS[0]), bidderCode: 'A'}, - {...deepClone(BID_REQUESTS[0]), bidderCode: 'B'}, - {...deepClone(BID_REQUESTS[0]), bidderCode: 'C'} - ]; - const chain1 = {chain: 1}; - const chain2 = {chain: 2}; + Object.entries({ + 'set': {}, + 'override': {source: {ext: {schain: 'pub-provided'}}} + }).forEach(([t, fpd]) => { + it(`should not ${t} source.ext.schain`, () => { + const bidderReqs = [ + {...deepClone(BID_REQUESTS[0]), bidderCode: 'A'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'B'}, + {...deepClone(BID_REQUESTS[0]), bidderCode: 'C'} + ]; + const chain1 = {chain: 1}; + const chain2 = {chain: 2}; - bidderReqs[0].bids[0].schain = chain1; - bidderReqs[1].bids[0].schain = chain2; - bidderReqs[2].bids[0].schain = chain2; + bidderReqs[0].bids[0].schain = chain1; + bidderReqs[1].bids[0].schain = chain2; + bidderReqs[2].bids[0].schain = chain2; - adapter.callBids(REQUEST, bidderReqs, addBidResponse, done, ajax); - const req = JSON.parse(server.requests[0].requestBody); - expect(req.source.ext.schain).to.eql(chain2); - }); + adapter.callBids({...REQUEST, ortb2Fragments: {global: fpd}}, bidderReqs, addBidResponse, done, ajax); + const req = JSON.parse(server.requests[0].requestBody); + expect(req.source?.ext?.schain).to.eql(fpd?.source?.ext?.schain); + }) + }) it('passes multibid array in request', function () { const bidRequests = utils.deepClone(BID_REQUESTS); @@ -3125,6 +3183,39 @@ describe('S2S Adapter', function () { expect(event[1].response).to.deep.equal(responding); }); + it('emits the PBS_ANALYTICS event and captures seatnonbid responses', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const nonbidResponse = {...RESPONSE_OPENRTB, ext: {seatnonbid: [{}]}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(nonbidResponse); + Object.assign(responding.ext.seatnonbid, [{auctionId: 2}]) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.thirdCall.args; + expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); + expect(event[1].seatnonbid[0]).to.have.property('auctionId', 2); + expect(event[1].requestedBidders).to.deep.equal(['appnexus']); + expect(event[1].response).to.deep.equal(responding); + }); + + it('emits the PBS_ANALYTICS event and captures atag responses', function () { + const original = CONFIG; + CONFIG.extPrebid = { returnallbidstatus: true }; + const atagResponse = {...RESPONSE_OPENRTB, ext: {prebid: {analytics: {tags: ['data']}}}}; + config.setConfig({ CONFIG }); + CONFIG = original; + adapter.callBids(REQUEST, BID_REQUESTS, addBidResponse, done, ajax); + const responding = deepClone(atagResponse); + Object.assign(responding.ext.prebid.analytics.tags, ['stuff']) + server.requests[0].respond(200, {}, JSON.stringify(responding)); + const event = events.emit.secondCall.args; + expect(event[0]).to.equal(EVENTS.PBS_ANALYTICS); + expect(event[1].atag[0]).to.deep.equal('stuff'); + expect(event[1].response).to.deep.equal(responding); + }); + it('respects defaultTtl', function () { const s2sConfig = Object.assign({}, CONFIG, { defaultTtl: 30 @@ -3445,6 +3536,18 @@ describe('S2S Adapter', function () { expect(response).to.have.property('adapterCode', 'appnexus2'); }); + it('should set deferBilling and deferRendering to true when request has deferBilling = true', () => { + config.setConfig({ CONFIG }); + const req = deepClone(REQUEST); + req.ad_units.forEach(au => au.deferBilling = true); + adapter.callBids(req, BID_REQUESTS, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(RESPONSE_OPENRTB)); + sinon.assert.match(addBidResponse.firstCall.args[1], { + deferBilling: true, + deferRendering: true + }); + }); + describe('on sync requested with no cookie', () => { let cfg, req, csRes; @@ -3508,21 +3611,24 @@ describe('S2S Adapter', function () { } before(() => { - addComponentAuction.before(fledgeHook); + addPaapiConfig.before(fledgeHook); }); after(() => { - addComponentAuction.getHooks({hook: fledgeHook}).remove(); + addPaapiConfig.getHooks({hook: fledgeHook}).remove(); }) beforeEach(function () { fledgeStub = sinon.stub(); - config.setConfig({CONFIG}); + config.setConfig({ + s2sConfig: CONFIG, + }); bidderRequests = deepClone(BID_REQUESTS); - AU bidderRequests.forEach(req => { Object.assign(req, { - fledgeEnabled: true, + paapi: { + enabled: true + }, ortb2: { fpd: 1 } @@ -3530,7 +3636,7 @@ describe('S2S Adapter', function () { req.bids.forEach(bid => { Object.assign(bid, { ortb2Imp: { - fpd: 2 + fpd: 2, } }) }) @@ -3541,22 +3647,36 @@ describe('S2S Adapter', function () { function expectFledgeCalls() { const auctionId = bidderRequests[0].auctionId; - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), {id: 1}) - sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), {id: 2}) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: bidderRequests[0].ortb2, ortb2Imp: bidderRequests[0].bids[0].ortb2Imp}), sinon.match({config: {id: 1}})) + sinon.assert.calledWith(fledgeStub, sinon.match({auctionId, adUnitCode: AU, ortb2: undefined, ortb2Imp: undefined}), sinon.match({config: {id: 2}})) } - it('calls addComponentAuction alongside addBidResponse', function () { + it('calls addPaapiConfig alongside addBidResponse', function () { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(mergeDeep({}, RESPONSE_OPENRTB, FLEDGE_RESP))); expect(addBidResponse.called).to.be.true; expectFledgeCalls(); }); - it('calls addComponentAuction when there is no bid in the response', () => { + it('calls addPaapiConfig when there is no bid in the response', () => { adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); expect(addBidResponse.called).to.be.false; expectFledgeCalls(); + }); + + it('wraps call in runWithBidder', () => { + let fail = false; + fledgeStub.callsFake(({bidder}) => { + try { + expect(bidder).to.exist.and.to.eql(config.getCurrentBidder()); + } catch (e) { + fail = true; + } + }); + adapter.callBids(request, bidderRequests, addBidResponse, done, ajax); + server.requests[0].respond(200, {}, JSON.stringify(FLEDGE_RESP)); + expect(fail).to.be.false; }) }); }); @@ -3705,181 +3825,135 @@ describe('S2S Adapter', function () { config.setConfig({ s2sConfig: options }); sinon.assert.calledOnce(logErrorSpy); }); + describe('vendor: appnexuspsp', () => { + 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 + }; - 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.deep.equal({ - p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync', - noP1Consent: 'https://prebid.adnxs-simple.com/pbs/v1/cookie_sync' + 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.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); }); - expect(vendorConfig).to.have.property('timeout', 750); - }); - - it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', function () { - const options = { - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - timeout: 750 - }; + }) - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', 'abc'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['rubicon']); - expect(vendorConfig.enabled).to.be.true; - 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); - }); + describe('vendor: rubicon', () => { + it('should configure the s2sConfig object with rubicon vendor defaults unless specified by user', function () { + const options = { + accountId: 'abc', + bidders: ['rubicon'], + defaultVendor: 'rubicon', + timeout: 750 + }; - it('should return proper defaults', function () { - const options = { - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - timeout: 750 - }; + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); - config.setConfig({ s2sConfig: options }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - 'accountId': 'abc', - 'adapter': 'prebidServer', - 'bidders': ['rubicon'], - 'defaultVendor': 'rubicon', - 'enabled': true, - 'endpoint': { + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', 'abc'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['rubicon']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ p1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction', noP1Consent: 'https://prebid-server.rubiconproject.com/openrtb2/auction' - }, - 'syncEndpoint': { + }); + expect(vendorConfig.syncEndpoint).to.deep.equal({ p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync' - }, - 'timeout': 750 - }) - }); - - it('should return default adapterOptions if not set', function () { - config.setConfig({ - s2sConfig: { + }); + expect(vendorConfig).to.have.property('timeout', 750); + }); + it('should return proper defaults', function () { + const options = { accountId: 'abc', bidders: ['rubicon'], defaultVendor: 'rubicon', timeout: 750 - } - }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - enabled: true, - timeout: 750, - adapter: 'prebidServer', - accountId: 'abc', - bidders: ['rubicon'], - defaultVendor: 'rubicon', - 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' - }, - }) - }); - - it('should configure the s2sConfig object with openwrap vendor defaults unless specified by user', function () { - const options = { - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap' - }; + }; - config.setConfig({ s2sConfig: options }); - sinon.assert.notCalled(logErrorSpy); - - let vendorConfig = config.getConfig('s2sConfig'); - expect(vendorConfig).to.have.property('accountId', '1234'); - expect(vendorConfig).to.have.property('adapter', 'prebidServer'); - expect(vendorConfig.bidders).to.deep.equal(['pubmatic']); - expect(vendorConfig.enabled).to.be.true; - expect(vendorConfig.endpoint).to.deep.equal({ - p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', - noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + config.setConfig({ s2sConfig: options }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': 'abc', + 'adapter': 'prebidServer', + 'bidders': ['rubicon'], + 'defaultVendor': 'rubicon', + 'enabled': true, + '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, + maxTimeout: 500, + }) }); - expect(vendorConfig).to.have.property('timeout', 500); - }); + }) - it('should return proper defaults', function () { - const options = { - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap', - timeout: 500 - }; + describe('vendor: openwrap', () => { + it('should configure the s2sConfig object with openwrap vendor defaults unless specified by user', function () { + const options = { + accountId: '1234', + bidders: ['pubmatic'], + defaultVendor: 'openwrap' + }; - config.setConfig({ s2sConfig: options }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - 'accountId': '1234', - 'adapter': 'prebidServer', - 'bidders': ['pubmatic'], - 'defaultVendor': 'openwrap', - 'enabled': true, - 'endpoint': { + config.setConfig({ s2sConfig: options }); + sinon.assert.notCalled(logErrorSpy); + + let vendorConfig = config.getConfig('s2sConfig'); + expect(vendorConfig).to.have.property('accountId', '1234'); + expect(vendorConfig).to.have.property('adapter', 'prebidServer'); + expect(vendorConfig.bidders).to.deep.equal(['pubmatic']); + expect(vendorConfig.enabled).to.be.true; + expect(vendorConfig.endpoint).to.deep.equal({ p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' - }, - 'timeout': 500 - }) - }); - - it('should return default adapterOptions if not set', function () { - config.setConfig({ - s2sConfig: { + }); + }); + it('should return proper defaults', function () { + const options = { accountId: '1234', bidders: ['pubmatic'], defaultVendor: 'openwrap', timeout: 500 - } + }; + + config.setConfig({ s2sConfig: options }); + expect(config.getConfig('s2sConfig')).to.deep.equal({ + 'accountId': '1234', + 'adapter': 'prebidServer', + 'bidders': ['pubmatic'], + 'defaultVendor': 'openwrap', + 'enabled': true, + 'endpoint': { + p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', + noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' + }, + 'timeout': 500, + maxTimeout: 500, + }) }); - expect(config.getConfig('s2sConfig')).to.deep.equal({ - enabled: true, - timeout: 500, - adapter: 'prebidServer', - accountId: '1234', - bidders: ['pubmatic'], - defaultVendor: 'openwrap', - endpoint: { - p1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs', - noP1Consent: 'https://ow.pubmatic.com/openrtb2/auction?source=pbjs' - }, - }) }); it('should set adapterOptions', function () { @@ -3922,77 +3996,94 @@ describe('S2S Adapter', function () { expect(typeof config.getConfig('s2sConfig').syncUrlModifier.appnexus).to.equal('function') }); - it('should set correct bidder names to bidders property when using an alias for that bidder', function () { - const s2sConfig = utils.deepClone(CONFIG); - - // 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 = { p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync' }; - s2sConfig.bidders = ['appnexus', 'rubicon-alias']; - - const s2sBidRequest = utils.deepClone(REQUEST); - s2sBidRequest.s2sConfig = s2sConfig; - - // Add another bidder, `rubicon-alias` - s2sBidRequest.ad_units[0].bids.push({ - bidder: 'rubicon-alias', - params: { - accoundId: 14062, - siteId: 70608, - zoneId: 498816 + Object.entries({ + 'an alias'() { + adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); + }, + 'a server side alias'(s2sConfig) { + s2sConfig.extPrebid = { + aliases: { + 'rubicon-alias': 'rubicon' + } } - }); - - // create an alias for the Rubicon Bid Adapter - adapterManager.aliasBidAdapter('rubicon', 'rubicon-alias'); + } + }).forEach(([t, setupAlias]) => { + describe(`when using ${t}`, () => { + afterEach(() => { + delete adapterManager.aliasRegistry['rubicon-alias']; + }); + it(`should set correct bidder names to bidders property`, function () { + const s2sConfig = utils.deepClone(CONFIG); + + // 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 = {p1Consent: 'https://prebid.adnxs.com/pbs/v1/cookie_sync'}; + s2sConfig.bidders = ['appnexus', 'rubicon-alias']; + + setupAlias(s2sConfig); + + const s2sBidRequest = utils.deepClone(REQUEST); + s2sBidRequest.s2sConfig = s2sConfig; + + // Add another bidder, `rubicon-alias` + s2sBidRequest.ad_units[0].bids.push({ + bidder: 'rubicon-alias', + params: { + accoundId: 14062, + siteId: 70608, + zoneId: 498816 + } + }); - const bidRequest = utils.deepClone(BID_REQUESTS); - bidRequest.push({ - 'bidderCode': 'rubicon-alias', - 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4', - 'bidderRequestId': '4b1a4f9c3e4546', - 'tid': 'd7fa8342-ae22-4ca1-b237-331169350f84', - 'bids': [ - { - 'bidder': 'rubicon-alias', - 'params': { - 'accountId': 14062, - 'siteId': 70608, - 'zoneId': 498816 - }, - 'bid_id': '2a9523915411c3', - 'mediaTypes': { - 'banner': { + const bidRequest = utils.deepClone(BID_REQUESTS); + bidRequest.push({ + 'bidderCode': 'rubicon-alias', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4', + 'bidderRequestId': '4b1a4f9c3e4546', + 'tid': 'd7fa8342-ae22-4ca1-b237-331169350f84', + 'bids': [ + { + 'bidder': 'rubicon-alias', + 'params': { + 'accountId': 14062, + 'siteId': 70608, + 'zoneId': 498816 + }, + 'bid_id': '2a9523915411c3', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 250 + ] + ] + } + }, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'transactionId': '78ddc106-b7d8-45d1-bd29-86993098e53d', 'sizes': [ [ 300, 250 ] - ] + ], + 'bidId': '2a9523915411c3', + 'bidderRequestId': '4b1a4f9c3e4546', + 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4' } - }, - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'transactionId': '78ddc106-b7d8-45d1-bd29-86993098e53d', - 'sizes': [ - [ - 300, - 250 - ] ], - 'bidId': '2a9523915411c3', - 'bidderRequestId': '4b1a4f9c3e4546', - 'auctionId': '4146ab2b-9422-4040-9b1c-966fffbfe2d4' - } - ], - 'auctionStart': 1569234122602, - 'timeout': 1000, - 'src': 's2s' - }); + 'auctionStart': 1569234122602, + 'timeout': 1000, + 'src': 's2s' + }); - adapter.callBids(s2sBidRequest, 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']); + 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 () { diff --git a/test/spec/modules/precisoBidAdapter_spec.js b/test/spec/modules/precisoBidAdapter_spec.js index 78a1615a02e..9a5ce1ae330 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,6 +1,9 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; -import { config } from '../../../src/config.js'; +import { NATIVE } from '../../../src/mediaTypes.js'; +import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils.js'; + +// simport { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 const DEFAULT_CURRENCY = 'USD' @@ -10,8 +13,10 @@ const BIDDER_CODE = 'preciso'; describe('PrecisoAdapter', function () { let bid = { + precisoBid: true, bidId: '23fhj33i987f', bidder: 'preciso', + buyerUid: 'testuid', mediaTypes: { banner: { sizes: [[DEFAULT_BANNER_WIDTH, DEFAULT_BANNER_HEIGHT]] @@ -22,15 +27,101 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'prebid-eu' + region: 'IND' + + }, + userId: { + pubcid: '12355454test' + + }, + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + } + + }; + + let nativeBid = { + + precisoBid: true, + bidId: '23fhj33i987f', + bidder: 'precisonat', + buyerUid: 'testuid', + params: { + host: 'prebid', + sourceid: '0', + publisherId: '0', + mediaType: 'native', + region: 'IND' }, userId: { pubcid: '12355454test' }, - geo: 'NA', - city: 'Asia,delhi' + user: { + geo: { + region: 'IND', + } + }, + ortb2: { + device: { + w: 1920, + h: 166, + dnt: 0, + }, + site: { + domain: 'localHost' + }, + source: { + tid: 'eaff09b-a1ab-4ec6-a81e-c5a75bc1dde3' + } + + }, + mediaType: 'native', + nativeOrtbRequest: { + assets: [ + { + id: 2, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + } + ] + }, + nativeParams: { + ortb: { + assets: [ + { + id: 2, + required: 1, + img: { + type: 3, + w: 300, + h: 250 + } + } + ] + } + } }; describe('isBidRequestValid', function () { @@ -41,6 +132,14 @@ describe('PrecisoAdapter', function () { delete bid.params.publisherId; expect(spec.isBidRequestValid(bid)).to.be.false; }); + it('Should return true if there are bidId, params and sourceid parameters present native Bid', function () { + expect(spec.isBidRequestValid(nativeBid)).to.be.true; + }); + + it('Should return false if at least one of parameters is not present in native bid', function () { + delete nativeBid.params.publisherId; + expect(spec.isBidRequestValid(nativeBid)).to.be.false; + }); }); describe('buildRequests', function () { @@ -59,43 +158,42 @@ describe('PrecisoAdapter', function () { }); 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('bidId', 'imp', 'site', 'deviceWidth', 'deviceHeight', 'language', 'secure', 'host', 'page', 'placements', 'coppa'); - - expect(data.deviceWidth).to.be.a('number'); - expect(data.deviceHeight).to.be.a('number'); - expect(data.coppa).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.city).to.be.a('string'); - expect(data.geo).to.be.a('object'); - // expect(data.userId).to.be.a('string'); - // expect(data.imp).to.be.a('object'); + expect(data).to.be.an('object'); + expect(data.device).to.be.a('object'); + expect(data.user).to.be.a('object'); + expect(data.source).to.be.a('object'); + expect(data.site).to.be.a('object'); }); - // it('Returns empty data if no valid requests are passed', function () { - /// serverRequest = spec.buildRequests([]); - // let data = serverRequest.data; - // expect(data.imp).to.be.an('array').that.is.empty; - // }); - }); - - describe('with COPPA', function () { - beforeEach(function () { - sinon.stub(config, 'getConfig') - .withArgs('coppa') - .returns(true); + it('Returns empty data if no valid requests are passed', function () { + delete bid.ortb2.device; + serverRequest = spec.buildRequests([bid]); + let data = serverRequest.data; + expect(data.device).to.be.undefined; }); - afterEach(function () { - config.getConfig.restore(); + + let ServeNativeRequest = spec.buildRequests([nativeBid]); + it('Creates a valid nativeServerRequest object ', function () { + expect(ServeNativeRequest).to.exist; + expect(ServeNativeRequest.method).to.exist; + expect(ServeNativeRequest.url).to.exist; + expect(ServeNativeRequest.data).to.exist; + expect(ServeNativeRequest.method).to.equal('POST'); + expect(ServeNativeRequest.url).to.equal('https://ssp-bidder.mndtrk.com/bid_request/openrtb'); }); - it('should send the Coppa "required" flag set to "1" in the request', function () { - let serverRequest = spec.buildRequests([bid]); - expect(serverRequest.data.coppa).to.equal(1); + it('should extract the native params', function () { + let nativeData = ServeNativeRequest.data; + const asset = JSON.parse(nativeData.imp[0].native.request).assets[0] + expect(asset).to.deep.equal({ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + w: 300, + h: 250, + type: OPENRTB.NATIVE.IMAGE_TYPE.MAIN, + } + } + ) }); }); @@ -143,11 +241,92 @@ describe('PrecisoAdapter', function () { expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])) }) + + it('should get correct native bid response', function () { + const adm = { + native: { + ver: 1.2, + link: { + url: 'https://example.com', + clicktrackers: 'https://example.com/clktracker' + }, + eventtrackers: [ + { + url: 'https://example.com/imptracker' + } + ], + imptrackers: [ + 'https://example.com/imptracker' + ], + assets: [{ + id: OPENRTB.NATIVE.ASSET_ID.IMAGE, + required: 1, + img: { + url: 'https://example.com/image.jpg', + w: 150, + h: 50 + } + }], + } + } + let nativeResponse = { + bidderRequestId: 'f6adb85f-4e19-45a0-b41e-2a5b9a48f23a', + seatbid: [ + { + bid: [ + { + id: '123', + impid: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + price: DEFAULT_PRICE, + adm: JSON.stringify(adm), + cid: 'test_cid', + crid: 'test_banner_crid', + w: DEFAULT_BANNER_WIDTH, + h: DEFAULT_BANNER_HEIGHT, + adomain: [], + } + ], + seat: BIDDER_CODE + } + ], + } + + let expectedNativeResponse = [ + { + requestId: 'b4f290d7-d4ab-4778-ab94-2baf06420b22', + mediaType: NATIVE, + cpm: DEFAULT_PRICE, + creativeId: 'test_banner_crid', + width: 1, + height: 1, + ttl: 300, + meta: { + advertiserDomains: [] + }, + netRevenue: true, + currency: 'USD', + // meta: { advertiserDomains: [] }, + native: { + clickUrl: encodeURI('https://example.com'), + impressionTrackers: ['https://example.com/imptracker'], + image: { + url: encodeURI('https://example.com/image.jpg'), + width: 150, + height: 50 + }, + } + } + ] + let result = spec.interpretResponse({ body: nativeResponse }); + expect(Object.keys(result[0])).to.have.members(Object.keys(expectedNativeResponse[0])); + }) }) + describe('getUserSyncs', function () { const syncUrl = 'https://ck.2trk.info/rtb/user/usersync.aspx?id=NA&gdpr=0&gdpr_consent=&us_privacy=&t=4'; const syncOptions = { - iframeEnabled: true + iframeEnabled: true, + spec: true }; let userSync = spec.getUserSyncs(syncOptions); it('Returns valid URL and type', function () { diff --git a/test/spec/modules/priceFloors_spec.js b/test/spec/modules/priceFloors_spec.js index 7223940bc45..41f1609f74f 100644 --- a/test/spec/modules/priceFloors_spec.js +++ b/test/spec/modules/priceFloors_spec.js @@ -1751,7 +1751,7 @@ describe('the price floors module', function () { const req = utils.deepClone(bidRequest); _floorDataForAuction[req.auctionId] = utils.deepClone(basicFloorConfig); - expect(guardTids('mock-bidder').bidRequest(req).getFloor({})).to.deep.equal({ + expect(guardTids({bidderCode: 'mock-bidder'}).bidRequest(req).getFloor({})).to.deep.equal({ currency: 'USD', floor: 1.0 }); @@ -2401,3 +2401,87 @@ describe('the price floors module', function () { }) }); }); + +describe('setting null as rule value', () => { + const nullFloorData = { + modelVersion: 'basic model', + modelWeight: 10, + modelTimestamp: 1606772895, + currency: 'USD', + schema: { + delimiter: '|', + fields: ['mediaType', 'size'] + }, + values: { + 'banner|600x300': null, + } + }; + + const basicBidRequest = { + bidder: 'rubicon', + adUnitCode: 'test_div_1', + auctionId: '1234-56-789', + transactionId: 'tr_test_div_1', + adUnitId: 'tr_test_div_1', + }; + + it('should validate for null values', function () { + let data = utils.deepClone(nullFloorData); + data.floorsSchemaVersion = 1; + expect(isFloorsDataValid(data)).to.to.equal(true); + }); + + it('getFloor should not return numeric value if null set as value', function () { + const bidRequest = { ...basicBidRequest, getFloor }; + const basicFloorConfig = { + enabled: true, + auctionDelay: 0, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: nullFloorData + } + _floorDataForAuction[bidRequest.auctionId] = basicFloorConfig; + + let inputParams = {mediaType: 'banner', size: [600, 300]}; + expect(bidRequest.getFloor(inputParams)).to.deep.equal(null); + }) + + it('getFloor should not return numeric value if null set as value - external floor provider', function () { + const basicFloorConfig = { + enabled: true, + auctionDelay: 0, + endpoint: {}, + enforcement: { + enforceJS: true, + enforcePBS: false, + floorDeals: false, + bidAdjustment: true + }, + data: nullFloorData + } + server.respondWith(JSON.stringify(nullFloorData)); + let exposedAdUnits; + + handleSetFloorsConfig({...basicFloorConfig, floorProvider: 'floorprovider', endpoint: {url: 'http://www.fakefloorprovider.json/'}}); + + const adUnits = [{ + cod: 'test_div_1', + mediaTypes: {banner: { sizes: [[600, 300]] }, native: {}}, + bids: [{bidder: 'someBidder', adUnitCode: 'test_div_1'}, {bidder: 'someOtherBidder', adUnitCode: 'test_div_1'}] + }]; + + requestBidsHook(config => exposedAdUnits = config.adUnits, { + auctionId: basicBidRequest.auctionId, + adUnits + }); + + let inputParams = {mediaType: 'banner', size: [600, 300]}; + + expect(exposedAdUnits[0].bids[0].getFloor(inputParams)).to.deep.equal(null); + }); +}) diff --git a/test/spec/modules/prismaBidAdapter_spec.js b/test/spec/modules/prismaBidAdapter_spec.js index be1c16c9059..b0d068e5614 100644 --- a/test/spec/modules/prismaBidAdapter_spec.js +++ b/test/spec/modules/prismaBidAdapter_spec.js @@ -3,7 +3,7 @@ import {spec} from 'modules/prismaBidAdapter.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 { requestBidsHook } from 'modules/consentManagementTcf.js'; describe('Prisma bid adapter tests', function () { const DISPLAY_BID_REQUEST = [{ diff --git a/test/spec/modules/proxistoreBidAdapter_spec.js b/test/spec/modules/proxistoreBidAdapter_spec.js index bcddb9e8b04..92e68f60b85 100644 --- a/test/spec/modules/proxistoreBidAdapter_spec.js +++ b/test/spec/modules/proxistoreBidAdapter_spec.js @@ -81,7 +81,7 @@ describe('ProxistoreBidAdapter', function () { it('should contain a valid url', function () { // has gdpr consent expect(request.url).equal(url.cookieBase); - // doens't have gpdr consent + // doens't have gdpr consent bidderRequest.gdprConsent.vendorData = null; request = spec.buildRequests([bid], bidderRequest); diff --git a/test/spec/modules/pstudioBidAdapter_spec.js b/test/spec/modules/pstudioBidAdapter_spec.js index 52ecb820ed3..2ec38e8dcda 100644 --- a/test/spec/modules/pstudioBidAdapter_spec.js +++ b/test/spec/modules/pstudioBidAdapter_spec.js @@ -18,7 +18,7 @@ describe('PStudioAdapter', function () { bidder: 'pstudio', params: { pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', - floorPrice: 1.15, + adtagid: 'aae1aabb-6699-4b5a-9c3f-9ed034b1932c', }, adUnitCode: 'test-div-1', mediaTypes: { @@ -38,7 +38,7 @@ describe('PStudioAdapter', function () { bidder: 'pstudio', params: { pubid: '258c2a8d-d2ad-4c31-a2a5-e63001186456', - floorPrice: 1.15, + adtagid: '34833639-f17c-40bc-9c4b-222b1b7459c7', }, adUnitCode: 'test-div-1', mediaTypes: { @@ -197,7 +197,7 @@ describe('PStudioAdapter', function () { it('should return false when publisher id not found', function () { const localBid = deepClone(bannerBid); delete localBid.params.pubid; - delete localBid.params.floorPrice; + delete localBid.params.adtagid; expect(spec.isBidRequestValid(localBid)).to.equal(false); }); @@ -232,7 +232,7 @@ describe('PStudioAdapter', function () { it('should properly map ids in request payload', function () { expect(bannerPayload.id).to.equal(bannerBid.bidId); - expect(bannerPayload.adtagid).to.equal(bannerBid.adUnitCode); + expect(bannerPayload.adtagid).to.equal(bannerBid.params.adtagid); }); it('should properly map banner mediaType in request payload', function () { @@ -266,7 +266,7 @@ describe('PStudioAdapter', function () { it('should properly set required bidder params in request payload', function () { expect(bannerPayload.pubid).to.equal(bannerBid.params.pubid); - expect(bannerPayload.floor_price).to.equal(bannerBid.params.floorPrice); + expect(bannerPayload.adtagid).to.equal(bannerBid.params.adtagid); }); it('should omit optional bidder params or first-party data from bid request if they are not provided', function () { diff --git a/test/spec/modules/pubCircleBidAdapter_spec.js b/test/spec/modules/pubCircleBidAdapter_spec.js index 8aaa023ee1c..083031101f1 100644 --- a/test/spec/modules/pubCircleBidAdapter_spec.js +++ b/test/spec/modules/pubCircleBidAdapter_spec.js @@ -1,11 +1,21 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/pubCircleBidAdapter'; +import { spec } from '../../../modules/pubCircleBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'pubcircle' +const bidder = 'pubcircle'; describe('PubCircleBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -16,8 +26,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -30,8 +41,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -53,8 +65,9 @@ describe('PubCircleBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -73,9 +86,20 @@ describe('PubCircleBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -112,6 +136,7 @@ describe('PubCircleBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -129,7 +154,7 @@ describe('PubCircleBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -145,6 +170,7 @@ describe('PubCircleBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -170,8 +196,10 @@ describe('PubCircleBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -186,12 +214,38 @@ describe('PubCircleBidAdapter', function () { 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); + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -395,5 +449,17 @@ describe('PubCircleBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal('https://cs.pubcircle.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.pubcircle.ai/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); }); }); diff --git a/test/spec/modules/pubgeniusBidAdapter_spec.js b/test/spec/modules/pubgeniusBidAdapter_spec.js index 86c8794dc4c..e1d579aaa4a 100644 --- a/test/spec/modules/pubgeniusBidAdapter_spec.js +++ b/test/spec/modules/pubgeniusBidAdapter_spec.js @@ -383,7 +383,6 @@ describe('pubGENIUS adapter', () => { w: 200, h: 100, startdelay: -1, - placement: 1, skip: 1, skipafter: 1, playbackmethod: [3, 4], diff --git a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js index 002b7fb3063..f77b167a3e9 100755 --- a/test/spec/modules/pubmaticAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubmaticAnalyticsAdapter_spec.js @@ -568,7 +568,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -786,7 +786,7 @@ describe('pubmatic analytics adapter', function () { expect(data.pid).to.equal('1111'); expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); expect(data.tgid).to.equal(0); @@ -866,7 +866,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tgid).to.equal(0);// test group id should be between 0-15 else set to 0 expect(data.fmv).to.equal('floorModelTest'); expect(data.ft).to.equal(1); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); // slot 1 @@ -1434,7 +1434,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1566,7 +1566,7 @@ describe('pubmatic analytics adapter', function () { expect(data.tst).to.equal(1519767016); expect(data.tgid).to.equal(15); expect(data.fmv).to.equal('floorModelTest'); - expect(data.pbv).to.equal(getGlobal()?.version || '-1'); + expect(data.pbv).to.equal('$prebid.version$' || '-1'); expect(data.ft).to.equal(1); expect(data.s).to.be.an('array'); expect(data.s.length).to.equal(2); @@ -1657,8 +1657,55 @@ describe('pubmatic analytics adapter', function () { expect(data.origbidid).to.equal('partnerImpressionID-1'); }); + it('Logger: should use originalRequestId to find the bid', function() { + MOCK.BID_RESPONSE[1]['originalRequestId'] = '3bd4ebb1c900e2'; + MOCK.BID_RESPONSE[1]['requestId'] = '54d4ebb1c9003e'; + 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.s).to.be.an('array'); + expect(data.s.length).to.equal(2); + + // slot 1 + expect(data.s[0].ps[0].bidid).to.equal('2ecff0db240757'); + expect(data.s[0].ps[0].origbidid).to.equal('partnerImpressionID-1'); + + // slot 2 + expect(data.s[1].ps[0].bidid).to.equal('54d4ebb1c9003e'); + expect(data.s[1].ps[0].origbidid).to.equal('partnerImpressionID-2'); + + // 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.bidid).to.equal('2ecff0db240757'); + expect(data.origbidid).to.equal('partnerImpressionID-1'); + }); + it('Logger: best case + win tracker. Log bidId when partnerimpressionid is missing', function() { delete MOCK.BID_RESPONSE[1]['partnerImpId']; + MOCK.BID_RESPONSE[1]['requestId'] = '3bd4ebb1c900e2'; MOCK.BID_RESPONSE[1]['prebidBidId'] = 'Prebid-bid-id-1'; sandbox.stub($$PREBID_GLOBAL$$, 'getHighestCpmBids').callsFake((key) => { return [MOCK.BID_RESPONSE[0], MOCK.BID_RESPONSE[1]] diff --git a/test/spec/modules/pubmaticBidAdapter_spec.js b/test/spec/modules/pubmaticBidAdapter_spec.js index ebda4b1767d..834db255e01 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType } from 'modules/pubmaticBidAdapter.js'; +import { spec, checkVideoPlacement, _getDomainFromURL, assignDealTier, prepareMetaObject, getDeviceConnectionType, setIBVField, setTTL } from 'modules/pubmaticBidAdapter.js'; import * as utils from 'src/utils.js'; import { config } from 'src/config.js'; import { createEidsArray } from 'modules/userId/eids.js'; @@ -1187,6 +1187,8 @@ describe('PubMatic adapter', function () { expect(data.imp[0].bidfloorcur).to.equal(bidRequests[0].params.currency); expect(data.source.ext.schain).to.deep.equal(bidRequests[0].schain); expect(data.ext.epoch).to.exist; + expect(data.imp[0].displaymanager).to.equal('Prebid.js'); + expect(data.imp[0].displaymanagerver).to.equal('$prebid.version$'); }); it('Set tmax from global config if not set by requestBids method', function() { @@ -2376,6 +2378,40 @@ describe('PubMatic adapter', function () { expect(data.device.ext).to.deep.equal(cdepObj); }); + it('should pass enriched device data from ortb2 object if present in bidderRequest fpd', function () { + const fpdBidderRequest = { + auctionId: 'new-auction-id', + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + } + }, + }; + + const request = spec.buildRequests(multipleMediaRequests, fpdBidderRequest); + const data = JSON.parse(request.data); + + expect(data.device.w).to.equal(fpdBidderRequest.ortb2.device.w); + expect(data.device.h).to.equal(fpdBidderRequest.ortb2.device.h); + expect(data.device.dnt).to.equal(fpdBidderRequest.ortb2.device.dnt); + expect(data.device.ua).to.equal(fpdBidderRequest.ortb2.device.ua); + expect(data.device.language).to.equal(fpdBidderRequest.ortb2.device.language); + expect(data.device.devicetype).to.equal(fpdBidderRequest.ortb2.device.devicetype); + expect(data.device.make).to.equal(fpdBidderRequest.ortb2.device.make); + expect(data.device.model).to.equal(fpdBidderRequest.ortb2.device.model); + expect(data.device.os).to.equal(fpdBidderRequest.ortb2.device.os); + expect(data.device.osv).to.equal(fpdBidderRequest.ortb2.device.osv); + }); + it('Request params should have valid native bid request for all valid params', function () { let request = spec.buildRequests(nativeBidRequests, { auctionId: 'new-auction-id' @@ -2946,7 +2982,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: false }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: false} }); let data = JSON.parse(req.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -2959,7 +2995,7 @@ describe('PubMatic adapter', function () { bidRequest[0].ortb2Imp = { ext: { ae: 1 } }; - const req = spec.buildRequests(bidRequest, { ...bidRequest, fledgeEnabled: true }); + const req = spec.buildRequests(bidRequest, { ...bidRequest, paapi: {enabled: true} }); let data = JSON.parse(req.data); expect(data.imp[0].ext.ae).to.equal(1); }); @@ -2972,6 +3008,24 @@ describe('PubMatic adapter', function () { expect(data.device).to.include.any.keys('connectiontype'); } }); + + it('should send imp.pmp in request if pmp json is present in adUnit ortb2Imp object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + originalBidRequests[0].ortb2Imp.pmp = { + 'private_auction': 0, + 'deals': [{ 'id': '5678' }] + } + const bidRequest = spec.buildRequests(originalBidRequests); + let data = JSON.parse(bidRequest.data); + expect(data.imp[0].pmp).to.exist.and.to.be.an('object'); + }) + + it('should not send imp.pmp in request if pmp json is not present in adUnit ortb2Imp object', function () { + let originalBidRequests = utils.deepClone(bidRequests); + const bidRequest = spec.buildRequests(originalBidRequests); + let data = JSON.parse(bidRequest.data); + expect(data.imp[0].pmp).to.deep.equal(undefined); + }) }); it('Request params dctr check', function () { @@ -3409,7 +3463,7 @@ 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(true); - expect(response[0].ttl).to.equal(300); + expect(response[0].ttl).to.equal(360); 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('seat-id'); @@ -3434,7 +3488,7 @@ 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(true); - expect(response[1].ttl).to.equal(300); + expect(response[1].ttl).to.equal(360); 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); @@ -3525,6 +3579,33 @@ describe('PubMatic adapter', function () { expect(response[0].renderer).to.not.exist; }); + it('should set ibv field in bid.ext when bid.ext.ibv exists', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + + let copyOfBidResponse = utils.deepClone(bannerBidResponse); + let bidExt = utils.deepClone(copyOfBidResponse.body.seatbid[0].bid[0].ext); + copyOfBidResponse.body.seatbid[0].bid[0].ext = Object.assign(bidExt, { + ibv: true + }); + + let response = spec.interpretResponse(copyOfBidResponse, request); + expect(response[0].ext.ibv).to.equal(true); + expect(response[0].meta.mediaType).to.equal('video'); + }); + + it('should not set ibv field when bid.ext does not exist ', function() { + let request = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + + let response = spec.interpretResponse(bannerBidResponse, request); + expect(response[0].ext).to.not.exist; + expect(response[0].meta).to.exist; + expect(response[0].meta.mediaType).to.equal('banner'); + }); + if (FEATURES.VIDEO) { it('should check for valid video mediaType in case of multiformat request', function() { let request = spec.buildRequests(videoBidRequests, { @@ -3784,9 +3865,9 @@ describe('PubMatic adapter', function () { response = spec.interpretResponse({ body: bidResponse }, bidRequest); it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test_bid_id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test_bid_id'); }); }); @@ -3824,10 +3905,12 @@ describe('PubMatic adapter', function () { // dchain: 'dc', // demandSource: 'ds', // secondaryCatIds: ['secondaryCatIds'] - } + }, }; - const br = {}; + const br = { + mediaType: 'video' + }; prepareMetaObject(br, bid, null); expect(br.meta.networkId).to.equal(6); // dspid expect(br.meta.buyerId).to.equal('12'); // adid @@ -3846,6 +3929,7 @@ describe('PubMatic adapter', function () { expect(br.meta.advertiserDomains).to.be.an('array').with.length.above(0); // adomain expect(br.meta.clickUrl).to.equal('mystartab.com'); // adomain expect(br.meta.dsa).to.equal(dsa); // dsa + expect(br.meta.mediaType).to.equal('video'); // mediaType }); it('Should be empty, when ext and adomain is absent in bid object', function () { @@ -4044,10 +4128,10 @@ describe('PubMatic adapter', function () { }); if (FEATURES.VIDEO) { - describe('Checking for Video.Placement property', function() { + describe('Checking for Video.plcmt property', function() { let sandbox, utilsMock; const adUnit = 'Div1'; - const msg_placement_missing = 'Video.Placement param missing for Div1'; + const msg_placement_missing = 'Video.plcmt param missing for Div1'; let videoData = { battr: [6, 7], skipafter: 15, @@ -4071,17 +4155,109 @@ describe('PubMatic adapter', function () { sandbox.restore(); }) - it('should log Video.Placement param missing', function() { + it('should log Video.plcmt param missing', function() { checkVideoPlacement(videoData, adUnit); sinon.assert.calledWith(utils.logWarn, msg_placement_missing); }) - it('shoud not log Video.Placement param missing', function() { - videoData['placement'] = 1; + it('shoud not log Video.plcmt param missing', function() { + videoData['plcmt'] = 1; checkVideoPlacement(videoData, adUnit); sinon.assert.neverCalledWith(utils.logWarn, msg_placement_missing); }) }); } + + describe('Banner Request param battr checking', function() { + it('should add battr params to bannerObj if present in ortb2Imp.banner', function() { + let originalBidRequests = utils.deepClone(bidRequests); + let bannerObj = utils.deepClone(originalBidRequests[0].ortb2Imp.banner); + originalBidRequests[0].ortb2Imp.banner = Object.assign(bannerObj, { + battr: [1, 2] + }); + + const req = spec.buildRequests(originalBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(req.data); + expect(data.imp[0]['banner']['battr']).to.exist.and.to.be.an('array'); + expect(data.imp[0]['banner']['battr'][0]).to.equal(originalBidRequests[0].ortb2Imp.banner['battr'][0]); + expect(data.imp[0]['banner']['battr'][1]).to.equal(originalBidRequests[0].ortb2Imp.banner['battr'][1]); + }); + + it('should not add battr params to bannerObj if not present in ortb2Imp.banner', function() { + const req = spec.buildRequests(bidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(req.data); + expect(data.imp[0]['banner']['battr']).to.equal(undefined); + }); + + it('should not add battr params if _checkParamDataType returns undefined (Mismatch data type)', function() { + let originalBidRequests = utils.deepClone(bidRequests); + let bannerObj = utils.deepClone(originalBidRequests[0].ortb2Imp.banner); + originalBidRequests[0].ortb2Imp.banner = Object.assign(bannerObj, { + battr: 1 + }); + + const req = spec.buildRequests(originalBidRequests, { + auctionId: 'new-auction-id' + }); + let data = JSON.parse(req.data); + expect(data.imp[0]['banner']['battr']).to.equal(undefined); + }); + }); + + describe('setIBVField', function() { + it('should set ibv field in newBid.ext when bid.ext.ibv exists', function() { + const bid = { + ext: { + ibv: true + } + }; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.exist; + expect(newBid.ext.ibv).to.equal(true); + expect(newBid.meta).to.exist; + expect(newBid.meta.mediaType).to.equal('video'); + }); + + it('should not set ibv field when bid.ext.ibv does not exist', function() { + const bid = { + ext: {} + }; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.not.exist; + expect(newBid.meta).to.not.exist; + }); + + it('should not set ibv field when bid.ext does not exist', function() { + const bid = {}; + const newBid = {}; + setIBVField(bid, newBid); + expect(newBid.ext).to.not.exist; + expect(newBid.meta).to.not.exist; + }); + + it('should preserve existing newBid.ext properties', function() { + const bid = { + ext: { + ibv: true + } + }; + const newBid = { + ext: { + existingProp: 'should remain' + } + }; + setIBVField(bid, newBid); + expect(newBid.ext.existingProp).to.equal('should remain'); + expect(newBid.ext.ibv).to.equal(true); + expect(newBid.meta).to.exist; + expect(newBid.meta.mediaType).to.equal('video'); + }); + }); }); if (FEATURES.VIDEO) { @@ -4207,4 +4383,49 @@ describe('PubMatic adapter', function () { expect(response[0].bidderCode).to.equal('groupm'); }); }); + + describe('setTTL', function() { + it('should set ttl field in newBid.ttl when bid.exp exists', function() { + const bid = { + exp: 200 + }; + const newBid = {}; + setTTL(bid, newBid); + expect(newBid.ttl).to.equal(200); + }); + + it('should set ttl as 360 mediatype banner', function() { + const bid = {}; + const newBid = { + mediaType: 'banner' + }; + setTTL(bid, newBid); + expect(newBid.ttl).to.equal(360); + }); + + it('should set ttl as 1800 mediatype video', function() { + const bid = {}; + const newBid = { + mediaType: 'video' + }; + setTTL(bid, newBid); + expect(newBid.ttl).to.equal(1800); + }); + + it('should set ttl as 1800 mediatype native', function() { + const bid = {}; + const newBid = { + mediaType: 'native' + }; + setTTL(bid, newBid); + expect(newBid.ttl).to.equal(1800); + }); + + it('should set ttl as 360 as default if all condition fails', function() { + const bid = {}; + const newBid = {}; + setTTL(bid, newBid); + expect(newBid.ttl).to.equal(360); + }); + }); }); diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js new file mode 100644 index 00000000000..e6dc710382c --- /dev/null +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -0,0 +1,515 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/pubriseBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'pubrise'; + +describe('PubriseBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + 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://backend.pubrise.ai/pbjs'); + }); + + it('Returns general data valid', function () { + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.all.keys( + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://sync.pubrise.ai/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://sync.pubrise.ai/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://sync.pubrise.ai/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/pubxBidAdapter_spec.js b/test/spec/modules/pubxBidAdapter_spec.js index b387264bf91..38efccac2a6 100644 --- a/test/spec/modules/pubxBidAdapter_spec.js +++ b/test/spec/modules/pubxBidAdapter_spec.js @@ -26,10 +26,10 @@ describe('pubxAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js index 9af1ef185e1..05b32dae434 100644 --- a/test/spec/modules/pubxaiAnalyticsAdapter_spec.js +++ b/test/spec/modules/pubxaiAnalyticsAdapter_spec.js @@ -1,675 +1,734 @@ -import pubxaiAnalyticsAdapter, {getBrowser, getDeviceType, 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'; -import {getGptSlotInfoForAdUnitCode} from '../../../libraries/gptUtils/gptUtils.js'; +/* globals describe, beforeEach, afterEach, sinon */ +import { expect } from 'chai'; +import { getGptSlotInfoForAdUnitCode } from 'libraries/gptUtils/gptUtils.js'; +import { getDeviceType, getBrowser, getOS } from 'libraries/userAgentUtils'; +import pubxaiAnalyticsAdapter, { + auctionCache, +} from 'modules/pubxaiAnalyticsAdapter.js'; import { EVENTS } from 'src/constants.js'; +import adapterManager from 'src/adapterManager.js'; +import { getWindowLocation } from 'src/utils.js'; +import { getGlobal } from 'src/prebidGlobal.js'; +import * as events from 'src/events.js' -let events = require('src/events'); +const readBlobSafariCompat = (blob) => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.onload = () => resolve(reader.result) + reader.onerror = reject + reader.readAsText(blob) + }) +} -describe('pubxai analytics adapter', function() { - beforeEach(function() { +describe('pubxai analytics adapter', () => { + beforeEach(() => { sinon.stub(events, 'getEvents').returns([]); + getGlobal().refreshUserIds?.() }); - afterEach(function() { + afterEach(() => { events.getEvents.restore(); }); - describe('track', function() { + describe('track', () => { + const pubxId = '6c415fc0-8b0e-4cf5-be73-01526a4db625'; let initOptions = { samplingRate: '1', - pubxId: '6c415fc0-8b0e-4cf5-be73-01526a4db625' + pubxId: pubxId, }; - let location = utils.getWindowLocation(); - let storage = window.top['sessionStorage']; + let originalVS; + + let location = getWindowLocation(); + + const replaceProperty = (obj, params) => { + let strObj = JSON.stringify(obj); + params.forEach(({ field, updated, replaced }) => { + strObj = strObj.replace( + new RegExp('"' + field + '":' + replaced, 'g'), + '"' + field + '":' + updated + ); + }); + return JSON.parse(strObj); + }; 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 + auctionInit: { + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + timestamp: 1603865707180, + auctionStatus: 'inProgress', + adUnits: [ + { + code: '/19968336/header-bid-tag-1', + mediaTypes: { + banner: { + sizes: [[300, 250]], + }, }, - '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' + 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', + }, ], - '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' + 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, + }, + ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + 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, }, - '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, }, - 'start': 1603865707182 - }], - 'noBids': [], - 'bidsReceived': [], - 'winningBids': [], - 'timeout': 1000, - 'config': { - 'samplingRate': '1', - 'pubxId': '6c415fc0-8b0e-4cf5-be73-01526a4db625' - } + ], + noBids: [], + bidsReceived: [], + winningBids: [], + timeout: 1000, + config: { + samplingRate: '1', + pubxId: pubxId, + }, }, - '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' + 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, }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + ], + ortb2: { + device: { + ext: { + cdep: true, + }, }, - '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 + 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 + 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 + 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 + 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', }, - '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' + 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 - ] - ] - } + 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', }, - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + ], + 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, + }, + ], + ortb2: { + device: { + ext: { + cdep: true, + }, + }, + }, + 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, }, - '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' + start: 1603865707182, + }, ], - 'bidderRequests': [{ - 'bidderCode': 'appnexus', - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'bidderRequestId': '184cbc05bb90ba', - 'bids': [{ - 'bidder': 'appnexus', - 'params': { - 'placementId': 13144370 + 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, }, - 'auctionId': 'bc3806e4-873e-453c-8ae5-204f35e923b4', - 'floorData': { - 'skipped': false, - 'skipRate': 0, - 'modelVersion': 'test model 1.0', - 'location': 'fetch', - 'floorProvider': 'PubXFloorProvider', - 'fetchStatus': 'success' + meta: { + advertiserId: 2529885, }, - 'mediaTypes': { - 'banner': { - 'sizes': [ - [ - 300, - 250 - ] - ] - } + 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', + }, }, - '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 + 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', }, - '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, + }, + ], }, - 'status': 'rendered', - 'params': [{ - 'placementId': 13144370 - }] - }], - 'winningBids': [], - 'timeout': 1000 + ], + 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 + 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 + 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 + 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', }, - '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' + 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 + status: 'rendered', + params: [ + { + placementId: 13144370, + }, + ], }, - 'pmcDetail': { - 'bidDensity': storage.getItem('pbx:dpbid'), - 'maxBid': storage.getItem('pbx:mxbid'), - 'auctionId': storage.getItem('pbx:aucid') - } }; let expectedAfterBid = { - 'bids': [{ - 'bidderCode': 'appnexus', - 'bidId': '248f9a4489835e', - 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': 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 + bids: [ + { + bidderCode: 'appnexus', + bidId: '248f9a4489835e', + adUnitCode: '/19968336/header-bid-tag-1', + gptSlotCode: + getGptSlotInfoForAdUnitCode('/19968336/header-bid-tag-1').gptSlot || + null, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + sizes: '300x250', + bidType: 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', + }, }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } + placementId: null, + timeToRespond: 267, + source: 'client', + responseTimestamp: 1616654313071, }, - 'timeToRespond': 267, - 'responseTimestamp': 1616654313071 - }], - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search, - 'adUnits': [ - '/19968336/header-bid-tag-1' - ] + ], + auctionDetail: { + adUnitCodes: ['/19968336/header-bid-tag-1'], + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + refreshRank: 0, + timestamp: 1616654312804, }, - 'floorDetail': { - 'fetchStatus': 'success', - 'floorProvider': 'PubXFloorProvider', - 'location': 'fetch', - 'modelVersion': 'test model 1.0', - 'skipRate': 0, - 'skipped': false + pageDetail: { + host: location.host, + path: location.pathname, + search: location.search, }, - 'deviceDetail': { - 'platform': navigator.platform, - 'deviceType': getDeviceType(), - 'deviceOS': getOS(), - 'browser': getBrowser() + floorDetail: { + fetchStatus: 'success', + floorProvider: 'PubXFloorProvider', + location: 'fetch', + modelVersion: 'test model 1.0', + skipRate: 0, + skipped: false, }, - 'pmcDetail': { - 'bidDensity': storage.getItem('pbx:dpbid'), - 'maxBid': storage.getItem('pbx:mxbid'), - 'auctionId': storage.getItem('pbx:aucid') + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), + cdep: true, + }, + userDetail: { + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), + }, + consentDetail: { + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), + }, + pmacDetail: {}, + extraData: {}, + initOptions: { + ...initOptions, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', }, - 'initOptions': initOptions }; let expectedAfterBidWon = { - 'winningBid': { - 'adUnitCode': '/19968336/header-bid-tag-1', - 'gptSlotCode': 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 + winningBid: { + adUnitCode: '/19968336/header-bid-tag-1', + gptSlotCode: + 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', }, - 'matchedFields': { - 'gptSlot': '/19968336/header-bid-tag-1', - 'mediaType': 'banner' - } }, - 'floorProvider': 'PubXFloorProvider', - 'floorFetchStatus': 'success', - 'floorLocation': 'fetch', - 'floorModelVersion': 'test model 1.0', - 'floorSkipRate': 0, - 'isFloorSkipped': false, - 'isWinningBid': true, - 'mediaType': 'banner', - 'netRevenue': true, - 'placementId': 13144370, - 'renderedSize': '300x250', - 'renderStatus': 4, - 'responseTimestamp': 1616654313071, - 'requestTimestamp': 1616654312804, - 'status': 'rendered', - 'statusMessage': 'Bid available', - 'timeToRespond': 267 + adServerData: {}, + floorProvider: 'PubXFloorProvider', + floorFetchStatus: 'success', + floorLocation: 'fetch', + floorModelVersion: 'test model 1.0', + floorSkipRate: 0, + isFloorSkipped: false, + isWinningBid: true, + mediaType: 'banner', + netRevenue: true, + placementId: 13144370, + renderedSize: '300x250', + sizes: '300x250', + bidType: 4, + responseTimestamp: 1616654313071, + requestTimestamp: 1616654312804, + status: 'rendered', + statusMessage: 'Bid available', + timeToRespond: 267, + source: 'client', + }, + auctionDetail: { + adUnitCodes: ['/19968336/header-bid-tag-1'], + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + refreshRank: 0, + timestamp: 1616654312804, + }, + pageDetail: { + host: location.host, + path: location.pathname, + search: location.search, }, - 'pageDetail': { - 'host': location.host, - 'path': location.pathname, - 'search': location.search + 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() + deviceDetail: { + platform: navigator.platform, + deviceType: getDeviceType(), + deviceOS: getOS(), + browser: getBrowser(), + cdep: true, }, - 'initOptions': initOptions - } + userDetail: { + userIdTypes: Object.keys(getGlobal().getUserIds?.() || {}), + }, + consentDetail: { + consentTypes: Object.keys(getGlobal().getConsentMetadata?.() || {}), + }, + pmacDetail: {}, + extraData: {}, + initOptions: { + ...initOptions, + auctionId: 'bc3806e4-873e-453c-8ae5-204f35e923b4', + }, + }; adapterManager.registerAnalyticsAdapter({ code: 'pubxai', - adapter: pubxaiAnalyticsAdapter + adapter: pubxaiAnalyticsAdapter, }); - beforeEach(function() { + beforeEach(() => { adapterManager.enableAnalytics({ provider: 'pubxai', - options: initOptions + options: initOptions, + }); + sinon.stub(navigator, 'sendBeacon').returns(true); + originalVS = document.visibilityState; + document['__defineGetter__']('visibilityState', function () { + return 'hidden'; }); }); - afterEach(function() { + afterEach(() => { pubxaiAnalyticsAdapter.disableAnalytics(); + navigator.sendBeacon.restore(); + delete auctionCache['bc3806e4-873e-453c-8ae5-204f35e923b4']; + delete auctionCache['auction2']; + document['__defineGetter__']('visibilityState', function () { + return originalVS; + }); }); - it('builds and sends auction data', function() { + it('builds and sends auction data', async () => { // Step 1: Send auction init event events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); @@ -685,20 +744,434 @@ describe('pubxai analytics adapter', function() { // Step 5: Send auction end event events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); - expect(server.requests.length).to.equal(1); + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); - let realAfterBid = JSON.parse(server.requests[0].requestBody); - - expect(realAfterBid).to.deep.equal(expectedAfterBid); + expect(navigator.sendBeacon.callCount).to.equal(0); // Step 6: Send auction bid won event events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); - expect(server.requests.length).to.equal(2); + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + expect(navigator.sendBeacon.callCount).to.equal(2); + + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index] + ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + ]); + } + }); + + it('auction data with only rejected bids', async () => { + // Step 1: Send auction init event + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid rejected (afaict the only expected reason would be a bid being too low) + events.emit(EVENTS.BID_REJECTED, prebidEvent['bidResponse']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 4: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 5: Send auction end event + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 6: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 8: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + + // Step 9: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + { + ...expectedAfterBid, + bids: [{ + ...expectedAfterBid.bids[0], + bidType: 1 + }] + } + ]); + }); + + it('auction data with only timed out bids', async () => { + // Step 1: Send auction init event + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send bid rejected (afaict the only expected reason would be a bid being too low) + events.emit(EVENTS.BID_TIMEOUT, [prebidEvent['bidResponse']]); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); - let winEventData = JSON.parse(server.requests[1].requestBody); + // Step 4: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); - expect(winEventData).to.deep.equal(expectedAfterBidWon); + // Step 5: Send auction end event + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 6: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 7: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 8: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + + // Step 9: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + { + ...expectedAfterBid, + bids: [{ + ...expectedAfterBid.bids[0], + bidType: 3 + }] + } + ]); + }); + + it('auction with no bids', async () => { + // Step 1: Send auction init event + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 3: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 4: Send auction end event + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 5: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(1); + + // Step 6: check the pathname of the calls is correct (sent only to the auction endpoint) + const [expectedUrl, expectedData] = navigator.sendBeacon.args[0]; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal('/analytics/auction'); + + // Step 7: check that the meta information in the call is correct + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + + // Step 8: check that the data sent in the request is correct + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + { + ...expectedAfterBid, + bids: [], + }, + ]); + }); + + it('2 concurrent auctions', async () => { + // Step 1: Send auction init event for auction 1 + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event for auction 1 + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send auction init event for auction 2 + events.emit( + EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent['auctionInit'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 4: Send bid requested event for auction 2 + events.emit( + EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent['bidRequested'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 5: Send bid response event for auction 1 + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 6: Send bid time out event for auction 1 + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 7: Send bid response event for auction 2 + events.emit( + EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent['bidResponse'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 8: Send auction end event for auction 1 + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 9: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Step 10: Send auction bid won event for auction 1 + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 11: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + + // Step 12: Send auction end event for auction 2 + events.emit( + EVENTS.AUCTION_END, + replaceProperty(prebidEvent['auctionEnd'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Simulate "navigate away" behaviour + document.dispatchEvent(new Event('visibilitychange')); + + // Step 13: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + + // Step 14: Send auction bid won event for auction 2 + events.emit( + EVENTS.BID_WON, + replaceProperty(prebidEvent['bidWon'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 15: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(4); + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + const auctionIdMapFn = index < 2 ? (i, _) => i : replaceProperty; + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index % 2] + ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + auctionIdMapFn([expectedAfterBidWon, expectedAfterBid][index % 2], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + { + field: 'refreshRank', + updated: '1', + replaced: '0', + }, + ]), + ]); + } + }); + + it('2 concurrent auctions with batch sending', async () => { + // Step 1: Send auction init event for auction 1 + events.emit(EVENTS.AUCTION_INIT, prebidEvent['auctionInit']); + + // Step 2: Send bid requested event for auction 1 + events.emit(EVENTS.BID_REQUESTED, prebidEvent['bidRequested']); + + // Step 3: Send auction init event for auction 2 + events.emit( + EVENTS.AUCTION_INIT, + replaceProperty(prebidEvent['auctionInit'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 4: Send bid requested event for auction 2 + events.emit( + EVENTS.BID_REQUESTED, + replaceProperty(prebidEvent['bidRequested'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 5: Send bid response event for auction 1 + events.emit(EVENTS.BID_RESPONSE, prebidEvent['bidResponse']); + + // Step 6: Send bid time out event for auction 1 + events.emit(EVENTS.BID_TIMEOUT, prebidEvent['bidTimeout']); + + // Step 7: Send bid response event for auction 2 + events.emit( + EVENTS.BID_RESPONSE, + replaceProperty(prebidEvent['bidResponse'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 8: Send auction end event for auction 1 + events.emit(EVENTS.AUCTION_END, prebidEvent['auctionEnd']); + + // Step 9: Send auction bid won event for auction 1 + events.emit(EVENTS.BID_WON, prebidEvent['bidWon']); + + // Step 10: Send auction end event for auction 2 + events.emit( + EVENTS.AUCTION_END, + replaceProperty(prebidEvent['auctionEnd'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 11: Send auction bid won event for auction 2 + events.emit( + EVENTS.BID_WON, + replaceProperty(prebidEvent['bidWon'], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + ]) + ); + + // Step 12: check the number of calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(0); + + // Simulate end of session + document.dispatchEvent(new Event('visibilitychange')); + + // Step 13: check the calls made to pubx.ai + expect(navigator.sendBeacon.callCount).to.equal(2); + for (const [index, arg] of navigator.sendBeacon.args.entries()) { + const [expectedUrl, expectedData] = arg; + const parsedUrl = new URL(expectedUrl); + expect(parsedUrl.pathname).to.equal( + ['/analytics/bidwon', '/analytics/auction'][index] + ); + expect(Object.fromEntries(parsedUrl.searchParams)).to.deep.equal({ + auctionTimestamp: '1616654312804', + pubxaiAnalyticsVersion: 'v2.1.0', + prebidVersion: '$prebid.version$', + pubxId: pubxId, + }); + expect(expectedData.type).to.equal('text/json'); + expect(JSON.parse(await readBlobSafariCompat(expectedData))).to.deep.equal([ + [expectedAfterBidWon, expectedAfterBid][index], + replaceProperty([expectedAfterBidWon, expectedAfterBid][index], [ + { + field: 'auctionId', + updated: '"auction2"', + replaced: '"bc3806e4-873e-453c-8ae5-204f35e923b4"', + }, + { + field: 'refreshRank', + updated: '1', + replaced: '0', + }, + ]), + ]); + } }); }); }); diff --git a/test/spec/modules/pubxaiRtdProvider_spec.js b/test/spec/modules/pubxaiRtdProvider_spec.js index b645b830246..6ffa4952992 100644 --- a/test/spec/modules/pubxaiRtdProvider_spec.js +++ b/test/spec/modules/pubxaiRtdProvider_spec.js @@ -1,6 +1,7 @@ import * as priceFloors from '../../../modules/priceFloors'; import { FLOORS_END_POINT, + storage, FLOORS_EVENT_HANDLE, FloorsApiStatus, beforeInit, @@ -45,6 +46,7 @@ const resetGlobals = () => { window.__pubxFloorsConfig__ = undefined; window.__pubxFloorsApiStatus__ = undefined; window.__pubxFloorRulesPromise__ = null; + localStorage.removeItem('pubx:dynamicFloors'); }; const fakeServer = ( @@ -119,7 +121,7 @@ describe('pubxaiRtdProvider', () => { stub.restore(); }); it('createFloorsDataForAuction called once before and once after __pubxFloorRulesPromise__. Also getBidRequestData executed only once', async () => { - pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => { }); assert(priceFloors.createFloorsDataForAuction.calledOnce); await window.__pubxFloorRulesPromise__; assert(priceFloors.createFloorsDataForAuction.calledTwice); @@ -129,7 +131,7 @@ describe('pubxaiRtdProvider', () => { reqBidsConfigObj.auctionId ) ); - pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => {}); + pubxaiSubmodule.getBidRequestData(reqBidsConfigObj, () => { }); await window.__pubxFloorRulesPromise__; assert(priceFloors.createFloorsDataForAuction.calledTwice); }); @@ -137,6 +139,16 @@ describe('pubxaiRtdProvider', () => { describe('fetchFloorRules', () => { const providerConfig = getConfig(); const floorsResponse = getFloorsResponse(); + let storageStub; + + beforeEach(() => { + storageStub = sinon.stub(storage, 'getDataFromSessionStorage'); + }); + + afterEach(() => { + storageStub.restore(); + }); + it('success with floors response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(floorsResponse); @@ -145,6 +157,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('success with no floors response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(undefined); @@ -153,6 +166,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('API call error', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer(undefined, undefined, 404); @@ -167,6 +181,7 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + it('Wrong API response', (done) => { const promise = fetchFloorRules(providerConfig); fakeServer('floorsResponse'); @@ -181,6 +196,25 @@ describe('pubxaiRtdProvider', () => { done(); }); }); + + it('success with local data response', (done) => { + const localFloorsResponse = getFloorsResponse(); + storageStub.withArgs('pubx:dynamicFloors').returns(JSON.stringify(localFloorsResponse)); + const promise = fetchFloorRules({ params: {} }); + promise.then((res) => { + expect(res).to.deep.equal(localFloorsResponse); + done(); + }); + }); + + it('no local data response', (done) => { + storageStub.withArgs('pubx:dynamicFloors').returns(null); + const promise = fetchFloorRules({ params: {} }); + promise.then((res) => { + expect(res).to.deep.equal(null); + done(); + }); + }); }); describe('setPriceFloors', () => { const providerConfig = getConfig(); @@ -383,9 +417,7 @@ describe('pubxaiRtdProvider', () => { expect(FLOORS_END_POINT).to.equal('https://floor.pbxai.com/'); }); it('standard case', () => { - expect(getUrl(provider)).to.equal( - `https://floor.pbxai.com/?pubxId=12345&page=${window.location.href}` - ); + expect(getUrl(provider)).to.equal(null); }); it('custom url provided', () => { provider.params.endpoint = 'https://custom.floor.com/'; diff --git a/test/spec/modules/pulsepointBidAdapter_spec.js b/test/spec/modules/pulsepointBidAdapter_spec.js index 8db7e909771..5b63e201e42 100644 --- a/test/spec/modules/pulsepointBidAdapter_spec.js +++ b/test/spec/modules/pulsepointBidAdapter_spec.js @@ -3,6 +3,9 @@ import {expect} from 'chai'; import {spec} from 'modules/pulsepointBidAdapter.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {deepClone} from '../../../src/utils'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; describe('PulsePoint Adapter Tests', function () { const slotConfigs = [{ @@ -362,7 +365,7 @@ describe('PulsePoint Adapter Tests', function () { const bidderRequestGdpr = { gdprConsent: { gdprApplies: true, - consentString: 'serialized_gpdr_data' + consentString: 'serialized_gdpr_data' } }; const request = spec.buildRequests(slotConfigs, syncAddFPDToBidderRequest(Object.assign({}, bidderRequest, bidderRequestGdpr))); @@ -372,7 +375,7 @@ describe('PulsePoint Adapter Tests', function () { // user object 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.user.ext.consent).to.equal('serialized_gdpr_data'); // regs object expect(ortbRequest.regs).to.not.equal(null); expect(ortbRequest.regs.ext).to.not.equal(null); @@ -484,22 +487,33 @@ describe('PulsePoint Adapter Tests', function () { it('Verify common id parameters', function () { const bidRequests = deepClone(slotConfigs); - bidRequests[0].userIdAsEids = [{ - source: 'pubcid.org', - uids: [{ - id: 'userid_pubcid' - }] - }, { - source: 'adserver.org', - uids: [{ - id: 'userid_ttd', - ext: { - rtiPartner: 'TDID' + const eids = [ + { + source: 'pubcid.org', + uids: [{ + id: 'userid_pubcid' + }] + }, { + source: 'adserver.org', + uids: [{ + id: 'userid_ttd', + ext: { + rtiPartner: 'TDID' + } + }] + } + ]; + const br = { + ...bidderRequest, + ortb2: { + user: { + ext: { + eids + } } - }] + } } - ]; - const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(br)); expect(request).to.be.not.null; expect(request.data).to.be.not.null; const ortbRequest = request.data; @@ -507,7 +521,7 @@ 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.deep.equal(bidRequests[0].userIdAsEids); + expect(ortbRequest.user.ext.eids).to.deep.equal(eids); }); it('Verify user level first party data', function () { @@ -518,7 +532,7 @@ describe('PulsePoint Adapter Tests', function () { }, gdprConsent: { gdprApplies: true, - consentString: 'serialized_gpdr_data' + consentString: 'serialized_gdpr_data' }, ortb2: { user: { @@ -545,7 +559,7 @@ describe('PulsePoint Adapter Tests', function () { registered: true, interests: ['cars'] }, - consent: 'serialized_gpdr_data' + consent: 'serialized_gdpr_data' } }); }); diff --git a/test/spec/modules/pxyzBidAdapter_spec.js b/test/spec/modules/pxyzBidAdapter_spec.js index 36e7a1e9ad6..87dc5ff0783 100644 --- a/test/spec/modules/pxyzBidAdapter_spec.js +++ b/test/spec/modules/pxyzBidAdapter_spec.js @@ -39,12 +39,12 @@ describe('pxyzBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/qortexRtdProvider_spec.js b/test/spec/modules/qortexRtdProvider_spec.js index c9f92e8af67..b5006e45d56 100644 --- a/test/spec/modules/qortexRtdProvider_spec.js +++ b/test/spec/modules/qortexRtdProvider_spec.js @@ -1,71 +1,93 @@ -import * as utils from 'src/utils'; -import * as ajax from 'src/ajax.js'; +import * as utils from 'src/utils.js'; import * as events from 'src/events.js'; import { EVENTS } from '../../../src/constants.js'; import {loadExternalScript} from 'src/adloader.js'; import { qortexSubmodule as module, - getContext, addContextToRequests, setContextData, + loadScriptTag, initializeModuleData, - loadScriptTag + setGroupConfigData, + requestContextData, + windowPostMessageReceived } from '../../../modules/qortexRtdProvider'; import {server} from '../../mocks/xhr.js'; import { cloneDeep } from 'lodash'; describe('qortexRtdProvider', () => { let logWarnSpy; + let logMessageSpy; let ortb2Stub; const defaultApiHost = 'https://demand.qortex.ai'; const defaultGroupId = 'test'; - const validBidderArray = ['qortex', 'test']; const validTagConfig = { videoContainer: 'my-video-container' } - const validModuleConfig = { - params: { - groupId: defaultGroupId, - apiUrl: defaultApiHost, - bidders: validBidderArray - } - }, - emptyModuleConfig = { - params: {} + params: { + groupId: defaultGroupId, + apiUrl: defaultApiHost, + bidders: validBidderArray, + enableBidEnrichment: true } - + } + const bidEnrichmentDisabledModuleConfig = { + params: { + groupId: defaultGroupId, + apiUrl: defaultApiHost, + bidders: validBidderArray + } + } + const invalidApiUrlModuleConfig = { + params: { + groupId: defaultGroupId, + apiUrl: 'test123', + bidders: validBidderArray + } + } + const emptyModuleConfig = { + params: {} + } const validImpressionEvent = { - detail: { - uid: 'uid123', - type: 'qx-impression' - } - }, - validImpressionEvent2 = { - detail: { - uid: 'uid1234', - type: 'qx-impression' - } - }, - missingIdImpressionEvent = { - detail: { - type: 'qx-impression' - } - }, - invalidTypeQortexEvent = { - detail: { - type: 'invalid-type' - } + detail: { + uid: 'uid123', + type: 'qx-impression' } - + } + const validImpressionEvent2 = { + detail: { + uid: 'uid1234', + type: 'qx-impression' + } + } + const missingIdImpressionEvent = { + detail: { + type: 'qx-impression' + } + } + const QortexPostMessageInitialized = { + target: 'QORTEX-PREBIDJS-RTD-MODULE', + message: 'CX-BID-ENRICH-INITIALIZED', + params: {groupConfig: {data: true}} + } + const QortexPostMessageContext = { + target: 'QORTEX-PREBIDJS-RTD-MODULE', + message: 'DISPATCH-CONTEXT', + params: {context: {data: true}} + } + const invalidTypeQortexEvent = { + detail: { + type: 'invalid-type' + } + } const responseHeaders = { 'content-type': 'application/json', 'access-control-allow-origin': '*' }; - - const responseObj = { + const contextResponseObj = { content: { id: '123456', episode: 15, @@ -74,11 +96,32 @@ describe('qortexRtdProvider', () => { season: '1', url: 'https://example.com/file.mp4' } - }; - - const apiResponse = JSON.stringify(responseObj); - + } + const contextResponse = JSON.stringify(contextResponseObj); + const validGroupConfigResponseObj = { + groupId: defaultGroupId, + active: true, + prebidBidEnrichment: true, + prebidBidEnrichmentPercentage: 100, + prebidReportingPercentage: 100 + } + const validGroupConfigResponse = JSON.stringify(validGroupConfigResponseObj); + const inactiveGroupConfigResponseObj = { + groupId: defaultGroupId, + active: false, + PrebidBidEnrichment: true, + PrebidReportingPercentage: 100 + } + const inactiveGroupConfigResponse = JSON.stringify(inactiveGroupConfigResponseObj); + const noEnrichmentGroupConfigResponseObj = { + groupId: defaultGroupId, + active: true, + prebidBidEnrichment: true, + prebidBidEnrichmentPercentage: 0, + prebidReportingPercentage: 100 + } const reqBidsConfig = { + auctionId: '1234', adUnits: [{ bids: [ { bidder: 'qortex' } @@ -93,17 +136,20 @@ describe('qortexRtdProvider', () => { beforeEach(() => { ortb2Stub = sinon.stub(reqBidsConfig, 'ortb2Fragments').value({bidder: {}, global: {}}) logWarnSpy = sinon.spy(utils, 'logWarn'); + logMessageSpy = sinon.spy(utils, 'logMessage'); }) afterEach(() => { logWarnSpy.restore(); + logMessageSpy.restore(); ortb2Stub.restore(); setContextData(null); }) describe('init', () => { - it('returns true for valid config object', () => { - expect(module.init(validModuleConfig)).to.be.true; + it('will not initialize bid enrichment if it is disabled', () => { + module.init(bidEnrichmentDisabledModuleConfig); + expect(logWarnSpy.calledWith('Bid Enrichment Function has been disabled in module configuration')).to.be.true; }) it('returns false and logs error for missing groupId', () => { @@ -168,21 +214,21 @@ describe('qortexRtdProvider', () => { dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); dispatchEvent(new CustomEvent('qortex-rtd', validImpressionEvent)); expect(billableEvents.length).to.be.equal(1); - expect(logWarnSpy.calledWith('received invalid billable event due to duplicate uid: qx-impression')).to.be.ok; + expect(logWarnSpy.calledWith('Received invalid billable event due to duplicate uid: qx-impression')).to.be.ok; }) it('will not allow events with missing uid', () => { loadScriptTag(config); dispatchEvent(new CustomEvent('qortex-rtd', missingIdImpressionEvent)); expect(billableEvents.length).to.be.equal(0); - expect(logWarnSpy.calledWith('received invalid billable event due to missing uid: qx-impression')).to.be.ok; + expect(logWarnSpy.calledWith('Received invalid billable event due to missing uid: qx-impression')).to.be.ok; }) it('will not allow events with unavailable type', () => { loadScriptTag(config); dispatchEvent(new CustomEvent('qortex-rtd', invalidTypeQortexEvent)); expect(billableEvents.length).to.be.equal(0); - expect(logWarnSpy.calledWith('received invalid billable event: invalid-type')).to.be.ok; + expect(logWarnSpy.calledWith('Received invalid billable event: invalid-type')).to.be.ok; }) }) @@ -191,7 +237,9 @@ describe('qortexRtdProvider', () => { beforeEach(() => { initializeModuleData(validModuleConfig); + setGroupConfigData(validGroupConfigResponseObj); callbackSpy = sinon.spy(); + server.reset(); }) afterEach(() => { @@ -203,80 +251,86 @@ describe('qortexRtdProvider', () => { const reqBidsConfigNoBids = { adUnits: [] }; module.getBidRequestData(reqBidsConfigNoBids, callbackSpy); expect(callbackSpy.calledOnce).to.be.true; - expect(logWarnSpy.calledWith('No adunits found on request bids configuration: ' + JSON.stringify(reqBidsConfigNoBids))).to.be.ok; + expect(logWarnSpy.calledOnce).to.be.true; }) - it('will call callback if getContext does not throw', () => { + it('will not request context if prebid disable toggle is true', (done) => { + initializeModuleData(bidEnrichmentDisabledModuleConfig); const cb = function () { - expect(logWarnSpy.calledOnce).to.be.false; + expect(server.requests.length).to.be.eql(0); + expect(logWarnSpy.called).to.be.true; + expect(logWarnSpy.calledWith('Bid enrichment disabled at prebid config')).to.be.true; done(); } module.getBidRequestData(reqBidsConfig, cb); - server.requests[0].respond(200, responseHeaders, apiResponse); }) - it('will catch and log error and fire callback', (done) => { - const a = sinon.stub(ajax, 'ajax').throws(new Error('test')); + it('will request to add context when ad units present and enabled', (done) => { const cb = function () { - expect(logWarnSpy.calledWith('test')).to.be.eql(true); + setContextData(null); + expect(server.requests.length).to.be.eql(0); + expect(logWarnSpy.called).to.be.true; + expect(logWarnSpy.calledWith('No context data received at this time')).to.be.true; done(); } module.getBidRequestData(reqBidsConfig, cb); - a.restore(); }) }) - describe('getContext', () => { + describe('onAuctionEndEvent', () => { beforeEach(() => { initializeModuleData(validModuleConfig); + setGroupConfigData(validGroupConfigResponseObj); }) afterEach(() => { initializeModuleData(emptyModuleConfig); + setGroupConfigData(null); }) - it('returns a promise', (done) => { - const result = getContext(); - expect(result).to.be.a('promise'); - done(); + it('Properly sends analytics event with valid config', () => { + const testData = {auctionId: reqBidsConfig.auctionId, data: 'data'}; + module.onAuctionEndEvent(testData); }) + }) - it('uses request url generated from initialize function in config and resolves to content object data', (done) => { - let requestUrl = `${validModuleConfig.params.apiUrl}/api/v1/analyze/${validModuleConfig.params.groupId}/prebid`; - const ctx = getContext() - expect(server.requests.length).to.be.eql(1); - expect(server.requests[0].url).to.be.eql(requestUrl); - server.requests[0].respond(200, responseHeaders, apiResponse); - ctx.then(response => { - expect(response).to.be.eql(responseObj.content); - done(); - }); + describe('requestContextData', () => { + before(() => { + setContextData({data: true}); }) - it('will return existing context data instead of ajax call if the source was not updated', (done) => { - setContextData(responseObj.content); - const ctx = getContext(); - expect(server.requests.length).to.be.eql(0); - ctx.then(response => { - expect(response).to.be.eql(responseObj.content); - done(); - }); + after(() => { + setContextData(null); }) - it('returns null for non erroring api responses other than 200', (done) => { - const nullContentResponse = { content: null } - const ctx = getContext() - server.requests[0].respond(200, responseHeaders, JSON.stringify(nullContentResponse)) - ctx.then(response => { - expect(response).to.be.null; - expect(server.requests.length).to.be.eql(1); - expect(logWarnSpy.called).to.be.false; - done(); - }); + it('Will log properly when context data already available', () => { + requestContextData(); + expect(logMessageSpy.calledWith('Context data already retrieved.')).to.be.true; }) }) - describe(' addContextToRequests', () => { + describe('addContextToRequests', () => { + let testReqBids; + beforeEach(() => { + setGroupConfigData(validGroupConfigResponseObj); + testReqBids = { + auctionId: '1234', + adUnits: [{ + bids: [ + { bidder: 'qortex' } + ] + }], + ortb2Fragments: { + bidder: {}, + global: {} + } + } + }) + + afterEach(() => { + setGroupConfigData(null); + }) + it('logs error if no data was retrieved from get context call', () => { initializeModuleData(validModuleConfig); addContextToRequests(reqBidsConfig); @@ -290,30 +344,30 @@ describe('qortexRtdProvider', () => { const omittedBidderArrayConfig = cloneDeep(validModuleConfig); delete omittedBidderArrayConfig.params.bidders; initializeModuleData(omittedBidderArrayConfig); - setContextData(responseObj.content); + setContextData(contextResponseObj.content); addContextToRequests(reqBidsConfig); expect(reqBidsConfig.ortb2Fragments.global).to.have.property('site'); expect(reqBidsConfig.ortb2Fragments.global.site).to.have.property('content'); - expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(responseObj.content); + expect(reqBidsConfig.ortb2Fragments.global.site.content).to.be.eql(contextResponseObj.content); expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) it('adds site.content only to bidder ortb2 when bidders array is included', () => { initializeModuleData(validModuleConfig); - setContextData(responseObj.content); + setContextData(contextResponseObj.content); addContextToRequests(reqBidsConfig); const qortexOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['qortex'] expect(qortexOrtb2Fragment).to.not.be.null; expect(qortexOrtb2Fragment).to.have.property('site'); expect(qortexOrtb2Fragment.site).to.have.property('content'); - expect(qortexOrtb2Fragment.site.content).to.be.eql(responseObj.content); + expect(qortexOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content); const testOrtb2Fragment = reqBidsConfig.ortb2Fragments.bidder['test'] expect(testOrtb2Fragment).to.not.be.null; expect(testOrtb2Fragment).to.have.property('site'); expect(testOrtb2Fragment.site).to.have.property('content'); - expect(testOrtb2Fragment.site.content).to.be.eql(responseObj.content); + expect(testOrtb2Fragment.site.content).to.be.eql(contextResponseObj.content); expect(reqBidsConfig.ortb2Fragments.global).to.be.eql({}); }) @@ -322,7 +376,7 @@ describe('qortexRtdProvider', () => { const invalidBidderArrayConfig = cloneDeep(validModuleConfig); invalidBidderArrayConfig.params.bidders = []; initializeModuleData(invalidBidderArrayConfig); - setContextData(responseObj.content) + setContextData(contextResponseObj.content) addContextToRequests(reqBidsConfig); expect(logWarnSpy.calledWith('Config contains an empty bidders array, unable to determine which bids to enrich')).to.be.ok; @@ -330,4 +384,25 @@ describe('qortexRtdProvider', () => { expect(reqBidsConfig.ortb2Fragments.bidder).to.be.eql({}); }) }) + + describe('initializeBidEnrichment', () => { + beforeEach(() => { + initializeModuleData(validModuleConfig); + setGroupConfigData(validGroupConfigResponseObj); + setContextData(null); + }) + + afterEach(() => { + setGroupConfigData(null); + setContextData(null); + }) + + it('processes incoming qortex component "initialize" message', () => { + windowPostMessageReceived({data: QortexPostMessageInitialized}) + }) + + it('processes incoming qortex component "context" message', () => { + windowPostMessageReceived({data: QortexPostMessageContext}) + }) + }) }) diff --git a/test/spec/modules/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js new file mode 100644 index 00000000000..f1c1ca61664 --- /dev/null +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -0,0 +1,514 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/qtBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'qt'; + +describe('QTBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + placementId: 'testBanner' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60 + } + }, + params: { + placementId: 'testVideo' + }, + userIdAsEids + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true + }, + body: { + required: true + }, + icon: { + required: true, + size: [64, 64] + } + } + } + }, + params: { + placementId: 'testNative' + }, + userIdAsEids + } + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + + } + } + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, + refererInfo: { + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } + }, + timeout: 500 + }; + + 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://endpoint1.qt.io/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', + 'device', + '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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) + }); + + 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://cs.qt.io/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://cs.qt.io/image?pbjs=1&ccpa_consent=1---&coppa=0') + }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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://cs.qt.io/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0') + }); + }); +}); diff --git a/test/spec/modules/quantcastBidAdapter_spec.js b/test/spec/modules/quantcastBidAdapter_spec.js index d10fea829bc..fdde8d290f4 100644 --- a/test/spec/modules/quantcastBidAdapter_spec.js +++ b/test/spec/modules/quantcastBidAdapter_spec.js @@ -181,7 +181,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3] // optional }, { context: 'instream', @@ -205,7 +204,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 @@ -242,7 +240,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, // optional playbackmethod: [1], // optional delivery: [1], // optional - placement: 1, // optional api: [2, 3], // optional context: 'instream', playerSize: [600, 300] @@ -265,7 +262,6 @@ describe('Quantcast adapter', function () { maxbitrate: 10, playbackmethod: [1], delivery: [1], - placement: 1, api: [2, 3], w: 600, h: 300 diff --git a/test/spec/modules/quantcastIdSystem_spec.js b/test/spec/modules/quantcastIdSystem_spec.js index e9d44dd6124..157c00e7567 100644 --- a/test/spec/modules/quantcastIdSystem_spec.js +++ b/test/spec/modules/quantcastIdSystem_spec.js @@ -1,6 +1,9 @@ import { quantcastIdSubmodule, storage, firePixel, hasCCPAConsent, hasGDPRConsent, checkTCFv2 } from 'modules/quantcastIdSystem.js'; import * as utils from 'src/utils.js'; import {coppaDataHandler} from 'src/adapterManager'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; describe('QuantcastId module', function () { beforeEach(function() { @@ -380,4 +383,23 @@ describe('Quantcast GDPR consent check', function() { } })).to.equal(false); }); + describe('eids', () => { + before(() => { + attachIdSystem(quantcastIdSubmodule); + }); + 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 + }] + }); + }); + }) }); diff --git a/test/spec/modules/r2b2BidAdapter_spec.js b/test/spec/modules/r2b2BidAdapter_spec.js index b94b400a71d..63850b78c40 100644 --- a/test/spec/modules/r2b2BidAdapter_spec.js +++ b/test/spec/modules/r2b2BidAdapter_spec.js @@ -420,7 +420,7 @@ describe('R2B2 adapter', function () { ], }, ]; - bids[0].userIdAsEids = eidsArray; + bidderRequest.ortb2 = {user: {ext: {eids: eidsArray}}} let requests = spec.buildRequests(bids, bidderRequest); let request = requests[0]; let eids = request.data.user.ext.eids; diff --git a/test/spec/modules/radsBidAdapter_spec.js b/test/spec/modules/radsBidAdapter_spec.js index 3ad7ada2ae7..4a64e2922f1 100644 --- a/test/spec/modules/radsBidAdapter_spec.js +++ b/test/spec/modules/radsBidAdapter_spec.js @@ -32,12 +32,12 @@ describe('radsAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/rakutenBidAdapter_spec.js b/test/spec/modules/rakutenBidAdapter_spec.js index 15b22afbe29..2a9fcb9f83b 100644 --- a/test/spec/modules/rakutenBidAdapter_spec.js +++ b/test/spec/modules/rakutenBidAdapter_spec.js @@ -40,10 +40,10 @@ describe('rakutenBidAdapter', function() { }); it('should return false when required params are not passed', () => { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false) + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false) }) }); diff --git a/test/spec/modules/raynRtdProvider_spec.js b/test/spec/modules/raynRtdProvider_spec.js index 69ea316e8b5..3920d090550 100644 --- a/test/spec/modules/raynRtdProvider_spec.js +++ b/test/spec/modules/raynRtdProvider_spec.js @@ -152,6 +152,7 @@ describe('rayn RTD Submodule', function () { 2: ['71', '313'], 4: ['33', '145', '712'] }; + TEST_SEGMENTS['103015'] = ['agdv23', 'avscg3']; const bidderOrtb2 = {}; const bidders = RTD_CONFIG.dataProviders[0].params.bidders; @@ -174,6 +175,9 @@ describe('rayn RTD Submodule', function () { TEST_SEGMENTS['4']['3'].forEach((id) => { expect(ortb2.user.data[0].segment.find(segment => segment.id === id)).to.exist; }); + TEST_SEGMENTS['103015'].forEach((id) => { + expect(ortb2.user.data[1].segment.find(segment => segment.id === id)).to.exist; + }); }); }); }); @@ -229,6 +233,27 @@ describe('rayn RTD Submodule', function () { logMessageSpy.restore(); }); + it('should update reqBidsConfigObj and execute callback using persona segment from localStorage', function () { + const callbackSpy = sinon.spy(); + const logMessageSpy = sinon.spy(utils, 'logMessage'); + const testSegments = { + 103015: ['agdv23', 'avscg3'] + }; + + getDataFromLocalStorageStub + .withArgs(raynRTD.RAYN_LOCAL_STORAGE_KEY) + .returns(JSON.stringify(testSegments)); + + const reqBidsConfigObj = { ortb2Fragments: { bidder: {} } }; + + raynRTD.raynSubmodule.getBidRequestData(reqBidsConfigObj, callbackSpy, RTD_CONFIG.dataProviders[0]); + + expect(callbackSpy.calledOnce).to.be.true; + expect(logMessageSpy.lastCall.lastArg).to.equal(`Segtax data from localStorage: ${JSON.stringify(testSegments)}`); + + logMessageSpy.restore(); + }); + it('should update reqBidsConfigObj and execute callback using segments from raynJS', function () { const callbackSpy = sinon.spy(); const logMessageSpy = sinon.spy(utils, 'logMessage'); diff --git a/test/spec/modules/relaidoBidAdapter_spec.js b/test/spec/modules/relaidoBidAdapter_spec.js index 4a07c84a494..5cb47eb8f51 100644 --- a/test/spec/modules/relaidoBidAdapter_spec.js +++ b/test/spec/modules/relaidoBidAdapter_spec.js @@ -115,6 +115,11 @@ describe('RelaidoAdapter', function () { }] } }; + window.RelaidoPlayer = { + renderAd: function() { + return null; + } + }; }); afterEach(() => { @@ -569,4 +574,20 @@ describe('RelaidoAdapter', function () { expect(query.ref).to.include(window.location.href); }); }); + + describe('spec.outstreamRender', function () { + it('Should to pass a Bid to renderAd', function () { + const bidResponses = spec.interpretResponse(serverResponse, serverRequest); + const response = bidResponses[0]; + sinon.spy(window.RelaidoPlayer, 'renderAd'); + response.renderer.render(response); + const renderCall = window.RelaidoPlayer.renderAd.getCall(0); + const arg = renderCall.args[0]; + expect(arg.width).to.equal(640); + expect(arg.height).to.equal(360); + expect(arg.vastXml).to.equal(''); + expect(arg.mediaType).to.equal(VIDEO); + expect(arg.placementId).to.equal(100000); + }); + }); }); diff --git a/test/spec/modules/relevatehealthBidAdapter_spec.js b/test/spec/modules/relevatehealthBidAdapter_spec.js new file mode 100644 index 00000000000..ef974bc3ac1 --- /dev/null +++ b/test/spec/modules/relevatehealthBidAdapter_spec.js @@ -0,0 +1,239 @@ +import { + expect +} from 'chai'; +import { + spec +} from '../../../modules/relevatehealthBidAdapter.js'; +import * as utils from '../../../src/utils.js'; + +describe('relevatehealth adapter', function() { + let request; + let bannerResponse, invalidResponse; + + beforeEach(function() { + request = [{ + bidder: 'relevatehealth', + mediaTypes: { + banner: { + sizes: [ + [160, 600] + ] + } + }, + params: { + placement_id: 110011, + user_id: '11211', + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }]; + bannerResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 20.68, + 'adid': '3389', + 'adm': "", + 'adomain': ['google.com'], + 'iurl': 'https://rtb.relevate.health/prebid/relevate', + 'cid': '1431/3389', + 'crid': '3389', + 'w': 160, + 'h': 600, + 'cat': ['IAB1-15'] + }], + 'seat': '00001', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1276' + } + }; + invalidResponse = { + 'body': { + 'id': 'a48b79e7-7104-4213-99f3-55f3234f2e54', + 'seatbid': [{ + 'bid': [{ + 'id': '3d7dd6dc-7665-4cdc-96a4-5ea192df32b8', + 'impid': '285b9c53b2c662', + 'price': 20.68, + 'adid': '3389', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://rtb.relevate.health/prebid/relevate', + 'cid': '1431/3389', + 'crid': '3389', + 'w': 160, + 'h': 600, + 'cat': ['IAB1-15'] + }], + 'seat': '00001', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_1276' + } + }; + }); + + describe('validations', function() { + it('isBidValid : placement_id and user_id are passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + placement_id: 110011, + user_id: '11211' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id and user_id are not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('isBidValid : placement_id is passed but user_id is not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + placement_id: 110011, + width: 160, + height: 600, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + it('isBidValid : user_id is passed but placement_id is not passed', function() { + let bid = { + bidder: 'relevatehealth', + params: { + width: 160, + height: 600, + domain: '', + bid_floor: 0.5, + user_id: '11211' + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Request', function() { + it('Immutable bid request validate', function() { + let _Request = utils.deepClone(request), + bidRequest = spec.buildRequests(request); + expect(request).to.deep.equal(_Request); + }); + it('Validate bidder connection', function() { + let _Request = spec.buildRequests(request); + expect(_Request.url).to.equal('https://rtb.relevate.health/prebid/relevate'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].id).to.equal(request[0].bidId); + expect(data[0].placementId).to.equal(110011); + }); + it('Validate bid request : ad size', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(160); + expect(data[0].imp[0].banner.h).to.equal(600); + }); + it('Validate bid request : user object', function() { + let _Request = spec.buildRequests(request); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function() { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(request, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].us_privacy).to.equal('1NYN'); + }); + }); + describe('Validate response ', function() { + it('Validate bid response : valid bid response', function() { + let bResponse = spec.interpretResponse(bannerResponse, request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function() { + let bRequest = spec.buildRequests(request); + let response = spec.interpretResponse(invalidResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('GPP and coppa', function() { + it('Request params check with GPP Consent', function() { + let bidderReq = { + gppConsent: { + gppString: 'gpp-string-test', + applicableSections: [5] + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].gpp).to.equal('gpp-string-test'); + expect(data[0].gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function() { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].gpp).to.equal('gpp-test-string'); + expect(data[0].gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { + ortb2: { + regs: { + coppa: 1 + } + } + }; + let _Request = spec.buildRequests(request, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].coppa).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/retailspotBidAdapter_spec.js b/test/spec/modules/retailspotBidAdapter_spec.js index 39cddb323b8..f1fb5ae3fd3 100644 --- a/test/spec/modules/retailspotBidAdapter_spec.js +++ b/test/spec/modules/retailspotBidAdapter_spec.js @@ -286,19 +286,19 @@ describe('RetailSpot Adapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.size; + let invalidBid = Object.assign({}, bid); + delete invalidBid.sizes; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placement': 0 }; - expect(!!spec.isBidRequestValid(bid)).to.equal(false); + expect(!!spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/rewardedInterestIdSystem_spec.js b/test/spec/modules/rewardedInterestIdSystem_spec.js new file mode 100644 index 00000000000..b6ce1e03f76 --- /dev/null +++ b/test/spec/modules/rewardedInterestIdSystem_spec.js @@ -0,0 +1,190 @@ +import sinon from 'sinon'; +import {expect} from 'chai'; +import * as utils from 'src/utils.js'; +import {attachIdSystem} from 'modules/userId'; +import {createEidsArray} from 'modules/userId/eids'; +import { + MODULE_NAME, + SOURCE, + getRewardedInterestApi, + watchRewardedInterestApi, + getRewardedInterestId, + apiNotAvailable, + rewardedInterestIdSubmodule +} from 'modules/rewardedInterestIdSystem.js'; + +describe('rewardedInterestIdSystem', () => { + const mockUserId = 'rewarded_interest_id'; + const mockApi = { + getApiVersion: () => '1.0', + getIdentityToken: () => Promise.resolve(mockUserId) + }; + const errorApiNotFound = `${MODULE_NAME} module: Rewarded Interest API not found`; + const errorIdFetch = `${MODULE_NAME} module: ID fetch encountered an error`; + let mockReadySate = 'complete'; + let logErrorSpy; + let callbackSpy; + + before(() => { + Object.defineProperty(document, 'readyState', { + get() { + return mockReadySate; + }, + }); + }); + + beforeEach(() => { + logErrorSpy = sinon.spy(utils, 'logError'); + callbackSpy = sinon.spy(); + }); + + afterEach(() => { + mockReadySate = 'complete'; + delete window.__riApi; + logErrorSpy.restore(); + }); + + describe('getRewardedInterestApi', () => { + it('should return Rewarded Interest Api if exists', () => { + expect(getRewardedInterestApi()).to.be.undefined; + window.__riApi = {}; + expect(getRewardedInterestApi()).to.be.undefined; + window.__riApi.getIdentityToken = mockApi.getIdentityToken; + expect(getRewardedInterestApi()).to.deep.equal(window.__riApi); + }); + }); + + describe('watchRewardedInterestApi', () => { + it('should execute callback when __riApi is set', () => { + watchRewardedInterestApi(callbackSpy); + expect(window.__riApi).to.be.undefined; + window.__riApi = mockApi; + expect(callbackSpy.calledOnceWithExactly(mockApi)).to.be.true; + expect(getRewardedInterestApi()).to.deep.equal(mockApi); + }); + }); + + describe('getRewardedInterestId', () => { + it('should get id from API and pass it to callback', async () => { + await getRewardedInterestId(mockApi, callbackSpy); + expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true; + }); + }); + + describe('apiNotAvailable', () => { + it('should call callback without ID and log error', () => { + apiNotAvailable(callbackSpy); + expect(callbackSpy.calledOnceWithExactly()).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true; + }); + }); + + describe('rewardedInterestIdSubmodule.name', () => { + it('should expose the name of the submodule', () => { + expect(rewardedInterestIdSubmodule).to.be.an.instanceof(Object); + expect(rewardedInterestIdSubmodule.name).to.equal(MODULE_NAME); + }); + }); + + describe('rewardedInterestIdSubmodule.decode', () => { + it('should wrap the given value inside an object literal', () => { + expect(rewardedInterestIdSubmodule.decode(mockUserId)).to.deep.equal({ [MODULE_NAME]: mockUserId }); + expect(rewardedInterestIdSubmodule.decode('')).to.be.undefined; + expect(rewardedInterestIdSubmodule.decode(null)).to.be.undefined; + }); + }); + + describe('rewardedInterestIdSubmodule.getId', () => { + it('should return object with callback property', () => { + const idResponse = rewardedInterestIdSubmodule.getId(); + expect(idResponse).to.be.an.instanceof(Object); + expect(idResponse).to.have.property('callback'); + expect(idResponse.callback).to.be.a('function'); + }); + + it('API not found, window loaded', async () => { + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + await Promise.resolve(); + expect(callbackSpy.calledOnceWithExactly()).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true; + }); + + it('API not found, window not loaded', async () => { + mockReadySate = 'loading'; + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + window.dispatchEvent(new Event('load')); + expect(callbackSpy.calledOnceWithExactly()).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(errorApiNotFound)).to.be.true; + }); + + it('API is set before getId, getIdentityToken return error', async () => { + const error = Error(); + window.__riApi = {getIdentityToken: () => Promise.reject(error)}; + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + await window.__riApi.getIdentityToken().catch(() => {}); + expect(callbackSpy.calledOnceWithExactly()).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(errorIdFetch, error)).to.be.true; + }); + + it('API is set after getId, getIdentityToken return error', async () => { + const error = Error(); + mockReadySate = 'loading'; + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + window.__riApi = {getIdentityToken: () => Promise.reject(error)}; + await window.__riApi.getIdentityToken().catch(() => {}); + expect(callbackSpy.calledOnceWithExactly()).to.be.true; + expect(logErrorSpy.calledOnceWithExactly(errorIdFetch, error)).to.be.true; + }); + + it('API is set before getId, getIdentityToken return user ID', async () => { + window.__riApi = mockApi; + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + await mockApi.getIdentityToken(); + expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true; + }); + + it('API is set after getId, getIdentityToken return user ID', async () => { + mockReadySate = 'loading'; + const idResponse = rewardedInterestIdSubmodule.getId(); + idResponse.callback(callbackSpy); + window.__riApi = mockApi; + window.dispatchEvent(new Event('load')); + await window.__riApi.getIdentityToken().catch(() => {}); + expect(callbackSpy.calledOnceWithExactly(mockUserId)).to.be.true; + }); + }); + + describe('rewardedInterestIdSubmodule.eids', () => { + it('should expose the eids of the submodule', () => { + expect(rewardedInterestIdSubmodule).to.have.property('eids'); + expect(rewardedInterestIdSubmodule.eids).to.be.a('object'); + expect(rewardedInterestIdSubmodule.eids).to.deep.equal({ + [MODULE_NAME]: { + source: SOURCE, + atype: 3, + }, + }); + }); + + it('createEidsArray', () => { + attachIdSystem(rewardedInterestIdSubmodule); + const eids = createEidsArray({ + [MODULE_NAME]: mockUserId + }); + expect(eids).to.be.a('array'); + expect(eids.length).to.equal(1); + expect(eids[0]).to.deep.equal({ + source: SOURCE, + uids: [{ + id: mockUserId, + atype: 3, + }] + }); + }); + }); +}); diff --git a/test/spec/modules/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index d2b173f53df..26d226b65f3 100644 --- a/test/spec/modules/richaudienceBidAdapter_spec.js +++ b/test/spec/modules/richaudienceBidAdapter_spec.js @@ -35,6 +35,53 @@ describe('Richaudience adapter tests', function () { user: {} }]; + var DEFAULT_PARAMS_NEW_DSA = [{ + adUnitCode: 'test-div', + bidId: '2c7c8e9c900244', + mediaTypes: { + banner: { + sizes: [ + [300, 250], [300, 600], [728, 90], [970, 250]] + } + }, + bidder: 'richaudience', + params: { + bidfloor: 0.5, + pid: 'ADb1f40rmi', + supplyType: 'site', + keywords: 'key1=value1;key2=value2' + }, + auctionId: '0cb3144c-d084-4686-b0d6-f5dbe917c563', + bidRequestsCount: 1, + bidderRequestId: '1858b7382993ca', + ortb2: { + regs: { + ext: { + dsa: { + dsarequired: 2, + pubrender: 1, + datatopub: 1, + transparency: [ + { + domain: 'richaudience.com', + dsaparams: [1, 3, 6] + }, + { + domain: 'adpone.com', + dsaparams: [8, 10, 12] + }, + { + domain: 'sunmedia.com', + dsaparams: [14, 16, 18] + } + ] + } + } + } + }, + user: {} + }]; + var DEFAULT_PARAMS_NEW_SIZES_GPID = [{ adUnitCode: 'test-div', bidId: '2c7c8e9c900244', @@ -211,7 +258,7 @@ describe('Richaudience adapter tests', function () { currency: 'USD', ttl: 300, dealId: 'dealId', - adomain: 'richaudience.com' + adomain: ['richaudience.com'] } }; @@ -227,7 +274,7 @@ describe('Richaudience adapter tests', function () { ttl: 300, vastXML: '', dealId: 'dealId', - adomain: 'richaudience.com' + adomain: ['richaudience.com'] } }; @@ -663,7 +710,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'); + expect(bid.meta.advertiserDomains[0]).to.equal('richaudience.com'); }); it('no banner media response inestream', function () { @@ -692,7 +739,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'); + expect(bid.meta.advertiserDomains[0]).to.equal('richaudience.com'); }); it('no banner media response outstream', function () { @@ -749,7 +796,7 @@ describe('Richaudience adapter tests', function () { it('Verifies bidder aliases', function () { expect(spec.aliases).to.have.lengthOf(1); - expect(spec.aliases[0]).to.equal('ra'); + expect(spec.aliases[0]).to.deep.equal({code: 'ra', gvlid: 108}); }); it('Verifies bidder gvlid', function () { @@ -893,6 +940,21 @@ describe('Richaudience adapter tests', function () { expect(requestContent).to.have.property('schain').to.deep.equal(schain); }) + it('should pass DSA', function() { + const request = spec.buildRequests(DEFAULT_PARAMS_NEW_DSA, { + gdprConsent: { + consentString: 'BOZcQl_ObPFjWAeABAESCD-AAAAjx7_______9______9uz_Ov_v_f__33e8__9v_l_7_-___u_-33d4-_1vf99yfm1-7ftr3tp_87ues2_Xur__59__3z3_NohBgA', + gdprApplies: true + }, + refererInfo: {} + }) + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('dsa').property('dsarequired').and.to.equal(2) + expect(requestContent).to.have.property('dsa').property('pubrender').and.to.equal(1); + expect(requestContent).to.have.property('dsa').property('datatopub').and.to.equal(1); + expect(requestContent.dsa.transparency[0]).to.have.property('domain').and.to.equal('richaudience.com'); + }) + it('should pass gpid', function() { const request = spec.buildRequests(DEFAULT_PARAMS_NEW_SIZES_GPID, { gdprConsent: { diff --git a/test/spec/modules/rasBidAdapter_spec.js b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js similarity index 89% rename from test/spec/modules/rasBidAdapter_spec.js rename to test/spec/modules/ringieraxelspringerBidAdapter_spec.js index f172d192221..3539dad9362 100644 --- a/test/spec/modules/rasBidAdapter_spec.js +++ b/test/spec/modules/ringieraxelspringerBidAdapter_spec.js @@ -1,10 +1,10 @@ import { expect } from 'chai'; -import { spec } from 'modules/rasBidAdapter.js'; +import { spec } from 'modules/ringieraxelspringerBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; const CSR_ENDPOINT = 'https://csr.onet.pl/4178463/csr-006/csr.json?nid=4178463&'; -describe('rasBidAdapter', function () { +describe('ringieraxelspringerBidAdapter', function () { const adapter = newBidder(spec); describe('inherited functions', function () { @@ -17,7 +17,7 @@ describe('rasBidAdapter', function () { it('should return true when required params found', function () { const bid = { sizes: [[300, 250], [300, 600]], - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { slot: 'slot', area: 'areatest', @@ -31,7 +31,7 @@ describe('rasBidAdapter', function () { it('should return false when required params not found', function () { const failBid = { sizes: [[300, 250], [300, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', params: { site: 'test', network: '4178463' @@ -43,7 +43,7 @@ describe('rasBidAdapter', function () { it('should return nothing when bid request is malformed', function () { const failBid = { sizes: [[300, 250], [300, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', }; expect(spec.isBidRequestValid(failBid)).to.equal(undefined); }); @@ -52,7 +52,7 @@ describe('rasBidAdapter', function () { describe('buildRequests', function () { const bid = { sizes: [[300, 250], [300, 600]], - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 1, params: { slot: 'test', @@ -81,7 +81,7 @@ describe('rasBidAdapter', function () { }; const bid2 = { sizes: [[750, 300]], - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 2, params: { slot: 'test2', @@ -157,8 +157,10 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('id0=1'); expect(requests[0].url).to.have.string('iusizes0=300x250%2C300x600'); expect(requests[0].url).to.have.string('slot1=test2'); + expect(requests[0].url).to.have.string('kvhb_format0=banner'); expect(requests[0].url).to.have.string('id1=2'); expect(requests[0].url).to.have.string('iusizes1=750x300'); + expect(requests[0].url).to.have.string('kvhb_format1=banner'); expect(requests[0].url).to.have.string('site=test'); expect(requests[0].url).to.have.string('area=areatest'); expect(requests[0].url).to.have.string('cre_format=html'); @@ -299,14 +301,14 @@ describe('rasBidAdapter', function () { } }]; const resp = spec.interpretResponse({body: {gctx: '1234567890'}}, bidRequest); - expect(resp).to.deep.equal({bids: [], fledgeAuctionConfigs: auctionConfigs}); + expect(resp).to.deep.equal({bids: [], paapi: auctionConfigs}); }); }); describe('buildNativeRequests', function () { const bid = { sizes: 'fluid', - bidder: 'ras', + bidder: 'ringieraxelspringer', bidId: 1, params: { slot: 'nativestd', @@ -365,6 +367,7 @@ describe('rasBidAdapter', function () { expect(requests[0].url).to.have.string('dr=https%3A%2F%2Fexample.org%2F'); expect(requests[0].url).to.have.string('test=name%3Dvalue'); expect(requests[0].url).to.have.string('cre_format0=native'); + expect(requests[0].url).to.have.string('kvhb_format0=native'); expect(requests[0].url).to.have.string('iusizes0=fluid'); }); }); @@ -407,6 +410,8 @@ describe('rasBidAdapter', function () { title: 'Headline', image: '//img.url', url: '//link.url', + partner_logo: '//logo.url', + adInfo: 'REKLAMA', impression: '//impression.url', impression1: '//impression1.url', impressionJs1: '//impressionJs1.url' @@ -444,9 +449,10 @@ describe('rasBidAdapter', function () { Calltoaction: 'Calltoaction', Headline: 'Headline', Image: '//img.url', - Sponsorlabel: 'nie', + adInfo: 'REKLAMA', Thirdpartyclicktracker: '//link.url', - imp: '//imp.url' + imp: '//imp.url', + thirdPartyClickTracker2: '//thirdPartyClickTracker.url' }, meta: { slot: 'nativestd', @@ -465,29 +471,54 @@ describe('rasBidAdapter', function () { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: '', + type: 2 + }, + }, + { + id: 1, + data: { + value: 'REKLAMA', + type: 10 + }, + }, + { + id: 3, img: { - url: '//img.url', + type: 1, + url: '//logo.url', w: 1, h: 1 } }, { id: 4, - title: { - text: 'Headline' + img: { + type: 3, + url: '//img.url', + w: 1, + h: 1 } }, { - id: 3, + id: 5, data: { value: 'Test Onet', type: 1 + }, + }, + { + id: 6, + title: { + text: 'Headline' } - } + }, ], link: { - url: '//adclick.url//link.url' + url: '//adclick.url//link.url', + clicktrackers: [] }, eventtrackers: [ { @@ -521,9 +552,15 @@ describe('rasBidAdapter', function () { width: 1, height: 1 }, + icon: { + url: '//logo.url', + width: 1, + height: 1 + }, clickUrl: '//adclick.url//link.url', cta: '', body: 'BODY', + body2: 'REKLAMA', sponsoredBy: 'Test Onet', ortb: expectedTeaserStandardOrtbResponse, privacyLink: '//dsa.url' @@ -532,29 +569,54 @@ describe('rasBidAdapter', function () { ver: '1.2', assets: [ { - id: 2, + id: 0, + data: { + value: '', + type: 2 + }, + }, + { + id: 1, + data: { + value: 'REKLAMA', + type: 10 + }, + }, + { + id: 3, img: { - url: '//img.url', + type: 1, + url: '', w: 1, h: 1 } }, { id: 4, - title: { - text: 'Headline' + img: { + type: 3, + url: '//img.url', + w: 1, + h: 1 } }, { - id: 3, + id: 5, data: { value: 'Test Onet', type: 1 + }, + }, + { + id: 6, + title: { + text: 'Headline' } - } + }, ], link: { - url: '//adclick.url//link.url' + url: '//adclick.url//link.url', + clicktrackers: ['//thirdPartyClickTracker.url'] }, eventtrackers: [ { @@ -578,9 +640,15 @@ describe('rasBidAdapter', function () { width: 1, height: 1 }, + icon: { + url: '', + width: 1, + height: 1 + }, clickUrl: '//adclick.url//link.url', cta: 'Calltoaction', body: 'BODY', + body2: 'REKLAMA', sponsoredBy: 'Test Onet', ortb: expectedNativeInFeedOrtbResponse, privacyLink: '//dsa.url' diff --git a/test/spec/modules/riseBidAdapter_spec.js b/test/spec/modules/riseBidAdapter_spec.js index 3cb5cb5c154..cb879231237 100644 --- a/test/spec/modules/riseBidAdapter_spec.js +++ b/test/spec/modules/riseBidAdapter_spec.js @@ -466,6 +466,8 @@ describe('riseAdapter', function () { height: 480, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: VIDEO }, { @@ -475,6 +477,8 @@ describe('riseAdapter', function () { height: 250, requestId: '21e12606d47ba7', adomain: ['abc.com'], + creativeId: 'creative-id', + nurl: 'http://example.com/win/1234', mediaType: BANNER }] }; @@ -486,7 +490,7 @@ describe('riseAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -501,10 +505,10 @@ describe('riseAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -517,8 +521,8 @@ describe('riseAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { diff --git a/test/spec/modules/rtbhouseBidAdapter_spec.js b/test/spec/modules/rtbhouseBidAdapter_spec.js index 77b746b9b69..dded1fe15a0 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; -import { OPENRTB, spec } from 'modules/rtbhouseBidAdapter.js'; +import { spec } from 'modules/rtbhouseBidAdapter.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config.js'; import { mergeDeep } from '../../../src/utils'; +import { OPENRTB } from '../../../libraries/precisoUtils/bidNativeUtils'; describe('RTBHouseAdapter', () => { const adapter = newBidder(spec); @@ -43,12 +44,12 @@ describe('RTBHouseAdapter', () => { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -460,7 +461,7 @@ describe('RTBHouseAdapter', () => { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: true }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: { enabled: true } }); expect(request.url).to.equal('https://prebid-eu.creativecdn.com/bidder/prebidfledge/bids'); expect(request.method).to.equal('POST'); }); @@ -470,7 +471,7 @@ describe('RTBHouseAdapter', () => { delete bidRequest[0].params.test; config.setConfig({ fledgeConfig: false }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -480,7 +481,7 @@ describe('RTBHouseAdapter', () => { expect(data.ext.fledge_config.sellerTimeout).to.equal(500); }); - it('sets a fledgeConfig object values when available from config', function () { + it('sets request.ext.fledge_config object values when available from fledgeConfig', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; @@ -490,7 +491,7 @@ describe('RTBHouseAdapter', () => { decisionLogicUrl: 'https://sellers.domain/decision.url' } }); - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); const data = JSON.parse(request.data); expect(data.ext).to.exist.and.to.be.a('object'); expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); @@ -500,13 +501,56 @@ describe('RTBHouseAdapter', () => { expect(data.ext.fledge_config.sellerTimeout).to.not.exist; }); + it('sets request.ext.fledge_config object values when available from paapiConfig', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ + paapiConfig: { + seller: 'https://sellers.domain', + decisionLogicUrl: 'https://sellers.domain/decision.url' + } + }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl'); + expect(data.ext.fledge_config.seller).to.equal('https://sellers.domain'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://sellers.domain/decision.url'); + expect(data.ext.fledge_config.sellerTimeout).to.not.exist; + }); + + it('sets request.ext.fledge_config object values when available from paapiConfig rather than from fledgeConfig if both exist', function () { + let bidRequest = Object.assign([], bidRequests); + delete bidRequest[0].params.test; + + config.setConfig({ + paapiConfig: { + seller: 'https://paapiconfig.sellers.domain', + decisionLogicUrl: 'https://paapiconfig.sellers.domain/decision.url' + }, + fledgeConfig: { + seller: 'https://fledgeconfig.sellers.domain', + decisionLogicUrl: 'https://fledgeconfig.sellers.domain/decision.url' + } + }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); + const data = JSON.parse(request.data); + expect(data.ext).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.exist.and.to.be.a('object'); + expect(data.ext.fledge_config).to.contain.keys('seller', 'decisionLogicUrl'); + expect(data.ext.fledge_config.seller).to.equal('https://paapiconfig.sellers.domain'); + expect(data.ext.fledge_config.decisionLogicUrl).to.equal('https://paapiconfig.sellers.domain/decision.url'); + }); + it('when FLEDGE is disabled, should not send imp.ext.ae', function () { let bidRequest = Object.assign([], bidRequests); delete bidRequest[0].params.test; bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: false }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: false} }); let data = JSON.parse(request.data); if (data.imp[0].ext) { expect(data.imp[0].ext).to.not.have.property('ae'); @@ -519,7 +563,7 @@ describe('RTBHouseAdapter', () => { bidRequest[0].ortb2Imp = { ext: { ae: 2 } }; - const request = spec.buildRequests(bidRequest, { ...bidderRequest, fledgeEnabled: true }); + const request = spec.buildRequests(bidRequest, { ...bidderRequest, paapi: {enabled: true} }); let data = JSON.parse(request.data); expect(data.imp[0].ext.ae).to.equal(2); }); @@ -782,9 +826,39 @@ describe('RTBHouseAdapter', () => { it('should return FLEDGE auction_configs alongside bids', function () { expect(response).to.have.property('bids'); - expect(response).to.have.property('fledgeAuctionConfigs'); - expect(response.fledgeAuctionConfigs.length).to.equal(1); - expect(response.fledgeAuctionConfigs[0].bidId).to.equal('test-bid-id'); + expect(response).to.have.property('paapi'); + expect(response.paapi.length).to.equal(1); + expect(response.paapi[0].bidId).to.equal('test-bid-id'); + }); + }); + + context('when the response contains FLEDGE auction config and bid request has additional signals in paapiConfig', function () { + let bidderRequest; + config.setConfig({ + paapiConfig: { + interestGroupBuyers: ['https://buyer1.com'], + perBuyerSignals: { + 'https://buyer1.com': { signal: 1 } + }, + customSignal: 1 + } + }); + let response = spec.interpretResponse({body: fledgeResponse}, {bidderRequest}); + + it('should have 2 buyers in interestGroupBuyers', function () { + expect(response.paapi[0].config.interestGroupBuyers.length).to.equal(2); + expect(response.paapi[0].config.interestGroupBuyers).to.have.members(['https://buyer1.com', 'https://buyer-domain.com']); + }); + + it('should have 2 perBuyerSignals with proper values', function () { + expect(response.paapi[0].config.perBuyerSignals).to.contain.keys('https://buyer1.com', 'https://buyer-domain.com'); + expect(response.paapi[0].config.perBuyerSignals['https://buyer1.com']).to.deep.equal({ signal: 1 }); + expect(response.paapi[0].config.perBuyerSignals['https://buyer-domain.com']).to.deep.equal({}); + }); + + it('should contain any custom signal passed via paapiConfig', function () { + expect(response.paapi[0].config).to.contain.keys('customSignal'); + expect(response.paapi[0].config.customSignal).to.equal(1); }); }); diff --git a/test/spec/modules/rubiconBidAdapter_spec.js b/test/spec/modules/rubiconBidAdapter_spec.js index 494943f9f7d..aff6c6df171 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -6,15 +6,15 @@ import { resetUserSync, classifiedAsVideo, resetRubiConf, + resetImpIdMap, converter } 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 'src/polyfill.js'; import {createEidsArray} from 'modules/userId/eids.js'; import 'modules/schain.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/userId/index.js'; import 'modules/priceFloors.js'; @@ -272,7 +272,7 @@ describe('the rubicon adapter', function () { }], criteoId: '1111', }; - bid.userIdAsEids = [ + const eids = [ { 'source': 'liveintent.com', 'uids': [ @@ -347,6 +347,7 @@ describe('the rubicon adapter', function () { ] } ]; + bidderRequest.ortb2 = {user: {ext: {eids}}}; return bidderRequest; } @@ -485,6 +486,7 @@ describe('the rubicon adapter', function () { utils.logError.restore(); config.resetConfig(); resetRubiConf(); + resetImpIdMap(); delete $$PREBID_GLOBAL$$.installedModules; }); @@ -515,7 +517,7 @@ describe('the rubicon adapter', function () { duplicate.bids[0].params.floor = 0.01; let [request] = spec.buildRequests(duplicate.bids, duplicate); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -547,9 +549,9 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); }); @@ -569,38 +571,38 @@ describe('the rubicon adapter', function () { }) ).to.be.true; - let data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + let data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // not an object should work and not send getFloorResponse = undefined; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with a non USD floor should not send it getFloorResponse = {currency: 'EUR', floor: 1.0}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with a non USD floor should not send it getFloorResponse = {currency: 'EUR'}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.be.undefined; + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.be.null; // make it respond with USD floor and string floor getFloorResponse = {currency: 'USD', floor: '1.23'}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.equal('1.23'); + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.equal('1.23'); // make it respond with USD floor and num floor getFloorResponse = {currency: 'USD', floor: 1.23}; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data.rp_hard_floor).to.equal('1.23'); + data = new URLSearchParams(request.data); + expect(data.get('rp_hard_floor')).to.equal('1.23'); }); it('should send rp_maxbids to AE if rubicon multibid config exists', function () { @@ -608,9 +610,9 @@ describe('the rubicon adapter', function () { multibidRequest.bidLimit = 5; let [request] = spec.buildRequests(multibidRequest.bids, multibidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['rp_maxbids']).to.equal('5'); + expect(data.get('rp_maxbids')).to.equal('5'); }); it('should not send p_pos to AE if not params.position specified', function () { @@ -618,10 +620,10 @@ describe('the rubicon adapter', function () { delete noposRequest.bids[0].params.position; let [request] = spec.buildRequests(noposRequest.bids, noposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should not send p_pos to AE if not mediaTypes.banner.pos is invalid', function () { @@ -634,10 +636,10 @@ describe('the rubicon adapter', function () { delete bidRequest.bids[0].params.position; let [request] = spec.buildRequests(bidRequest.bids, bidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should send p_pos to AE if mediaTypes.banner.pos is valid', function () { @@ -650,10 +652,10 @@ describe('the rubicon adapter', function () { delete bidRequest.bids[0].params.position; let [request] = spec.buildRequests(bidRequest.bids, bidRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal('atf'); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal('atf'); }); it('should not send p_pos to AE if not params.position is invalid', function () { @@ -661,10 +663,10 @@ describe('the rubicon adapter', function () { badposRequest.bids[0].params.position = 'bad'; let [request] = spec.buildRequests(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['site_id']).to.equal('70608'); - expect(data['p_pos']).to.equal(undefined); + expect(data.get('site_id')).to.equal('70608'); + expect(data.get('p_pos')).to.equal(null); }); it('should correctly send p_pos in sra fashion', function() { @@ -693,9 +695,9 @@ describe('the rubicon adapter', function () { sraPosRequest.bids.push(bidCopy3); let [request] = spec.buildRequests(sraPosRequest.bids, sraPosRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['p_pos']).to.equal('atf;;btf;;'); + expect(data.get('p_pos')).to.equal('atf;;btf;;'); }); it('should correctly send cdep signal when requested', () => { @@ -703,9 +705,31 @@ describe('the rubicon adapter', function () { badposRequest.bids[0].ortb2 = {device: {ext: {cdep: 3}}}; let [request] = spec.buildRequests(badposRequest.bids, badposRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['o_cdep']).to.equal('3'); + expect(data.get('o_cdep')).to.equal('3'); + }); + + it('should correctly send ip signal when ortb2.device.ip is provided', () => { + const ipRequest = utils.deepClone(bidderRequest); + ipRequest.bids[0].ortb2 = { device: { ip: '123.45.67.89' } }; + + let [request] = spec.buildRequests(ipRequest.bids, ipRequest); + let data = new URLSearchParams(request.data); + + // Verify if 'ip' is correctly added to the request data + expect(data.get('ip')).to.equal('123.45.67.89'); + }); + + it('should correctly send ipv6 signal when ortb2.device.ipv6 is provided', () => { + const ipv6Request = utils.deepClone(bidderRequest); + ipv6Request.bids[0].ortb2 = { device: { ipv6: '2001:db8::ff00:42:8329' } }; + + let [request] = spec.buildRequests(ipv6Request.bids, ipv6Request); + let data = new URLSearchParams(request.data); + + // Verify if 'ipv6' is correctly added to the request data + expect(data.get('ipv6')).to.equal('2001:db8::ff00:42:8329'); }); it('ad engine query params should be ordered correctly', function () { @@ -741,15 +765,15 @@ describe('the rubicon adapter', function () { 'tg_i.rating': '5-star', 'tg_i.prodtype': 'tech,mobile', 'rf': 'localhost', - 'p_geo.latitude': undefined, - 'p_geo.longitude': undefined + 'p_geo.latitude': null, + 'p_geo.longitude': null }; sandbox.stub(Math, 'random').callsFake(() => 0.1); delete bidderRequest.bids[0].params.latLong; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -757,15 +781,15 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); bidderRequest.bids[0].params.latLong = []; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); + data = new URLSearchParams(request.data); expect(request.url).to.equal('https://fastlane.rubiconproject.com/a/api/fastlane.json'); @@ -773,9 +797,9 @@ describe('the rubicon adapter', function () { Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; if (value instanceof RegExp) { - expect(data[key]).to.match(value); + expect(data.get(key)).to.match(value); } else { - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); } }); }); @@ -795,24 +819,24 @@ describe('the rubicon adapter', function () { delete bidderRequest.bids[0].params.referrer; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.exist; - expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); + expect(new URLSearchParams(request.data).get('rf')).to.exist; + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); }); it('page_url should use params.referrer, bidderRequest.refererInfo in that order', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.equal('localhost'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('localhost'); delete bidderRequest.bids[0].params.referrer; let refererInfo = {page: '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'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); bidderRequest.refererInfo.page = 'http://www.prebid.org'; bidderRequest.bids[0].params.secure = true; [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - expect(parseQuery(request.data).rf).to.equal('https://www.prebid.org'); + expect(new URLSearchParams(request.data).get('rf')).to.equal('https://www.prebid.org'); }); it('should use rubicon sizes if present (including non-mappable sizes)', function () { @@ -820,10 +844,10 @@ describe('the rubicon adapter', function () { sizesBidderRequest.bids[0].params.sizes = [55, 57, 59, 801]; let [request] = spec.buildRequests(sizesBidderRequest.bids, sizesBidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['size_id']).to.equal('55'); - expect(data['alt_size_ids']).to.equal('57,59,801'); + expect(data.get('size_id')).to.equal('55'); + expect(data.get('alt_size_ids')).to.equal('57,59,801'); }); it('should not validate bid request if no valid sizes', function () { @@ -849,48 +873,48 @@ describe('the rubicon adapter', function () { floorBidderRequest.bids[0].params.floor = 2; let [request] = spec.buildRequests(floorBidderRequest.bids, floorBidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['rp_floor']).to.equal('2'); + expect(data.get('rp_floor')).to.equal('2'); }); describe('GDPR consent config', function () { it('should send "gdpr" and "gdpr_consent", when gdprConsent defines consentString and gdprApplies', function () { const bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr']).to.equal('1'); - expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(data.get('gdpr')).to.equal('1'); + expect(data.get('gdpr_consent')).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); }); it('should send only "gdpr_consent", when gdprConsent defines only consentString', function () { const bidderRequest = createGdprBidderRequest(); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr_consent']).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); - expect(data['gdpr']).to.equal(undefined); + expect(data.get('gdpr_consent')).to.equal('BOJ/P2HOJ/P2HABABMAAAAAZ+A=='); + expect(data.get('gdpr')).to.equal(null); }); it('should not send GDPR params if gdprConsent is not defined', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gdpr']).to.equal(undefined); - expect(data['gdpr_consent']).to.equal(undefined); + expect(data.get('gdpr')).to.equal(null); + expect(data.get('gdpr_consent')).to.equal(null); }); it('should set "gdpr" value as 1 or 0, using "gdprApplies" value of either true/false', function () { let bidderRequest = createGdprBidderRequest(true); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); - expect(data['gdpr']).to.equal('1'); + let data = new URLSearchParams(request.data); + expect(data.get('gdpr')).to.equal('1'); bidderRequest = createGdprBidderRequest(false); [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - data = parseQuery(request.data); - expect(data['gdpr']).to.equal('0'); + data = new URLSearchParams(request.data); + expect(data.get('gdpr')).to.equal('0'); }); }); @@ -898,16 +922,16 @@ describe('the rubicon adapter', function () { it('should send us_privacy if bidderRequest has a value for uspConsent', function () { addUspToBidderRequest(bidderRequest); let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['us_privacy']).to.equal('1NYN'); + expect(data.get('us_privacy')).to.equal('1NYN'); }); it('should not send us_privacy if bidderRequest has no uspConsent value', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['us_privacy']).to.equal(undefined); + expect(data.get('us_privacy')).to.equal(null); }); }); @@ -918,19 +942,19 @@ describe('the rubicon adapter', function () { applicableSections: 2 }; let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); delete bidderRequest.gppConsent; - expect(data['gpp']).to.equal('consent'); - expect(data['gpp_sid']).to.equal('2'); + expect(data.get('gpp')).to.equal('consent'); + expect(data.get('gpp_sid')).to.equal('2'); }); it('should not send gpp information if bidderRequest does not have a value for gppConsent', function () { let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['gpp']).to.equal(undefined); - expect(data['gpp_sid']).to.equal(undefined); + expect(data.get('gpp')).to.equal(null); + expect(data.get('gpp_sid')).to.equal(null); }); }); @@ -953,13 +977,14 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure that no tg_v or tg_i keys are present in the request - let matchingExp = RegExp('^tg_(i|v)\..*$') - Object.keys(data).forEach(key => { + let matchingExp = RegExp('^tg_(i|v)\..*$'); + // Display the keys + for (const key of data.keys()) { expect(key).to.not.match(matchingExp); - }); + } }); it('should contain valid params when some are undefined', function () { @@ -985,17 +1010,17 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure none of the undefined keys are in query undefinedKeys.forEach(key => { - expect(typeof data[key]).to.equal('undefined'); + expect(data.get(key)).to.equal(null); }); // make sure the expected and defined ones do show up still Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(data[key]).to.equal(value); + expect(data.get(key)).to.equal(value); }); }); @@ -1079,12 +1104,12 @@ describe('the rubicon adapter', function () { // get the built request let [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // make sure that tg_v, tg_i, and kw values are correct Object.keys(expectedQuery).forEach(key => { let value = expectedQuery[key]; - expect(data[key]).to.deep.equal(value); + expect(data.get(key)).to.deep.equal(value); }); }); }); @@ -1163,13 +1188,13 @@ describe('the rubicon adapter', function () { expect(bidRequestItem.params.siteId).to.equal(array[0].params.siteId); }); - const data = parseQuery(item.data); + const data = new URLSearchParams(item.data); Object.keys(expectedQuery).forEach(key => { - expect(data).to.have.property(key); + expect(data.get(key)).to.be.exist; // extract semicolon delineated values - const params = data[key].split(';'); + const params = data.get(key).split(';'); // skip value test for site and zone ids if (key !== 'site_id' && key !== 'zone_id') { @@ -1218,10 +1243,10 @@ describe('the rubicon adapter', function () { // check that slots param value matches expect(serverRequests[0].data.indexOf('&slots=10&') !== -1).to.equal(true); // check that zone_id has 10 values (since all zone_ids are unique all should exist in get param) - data = parseQuery(serverRequests[0].data); - expect(data).to.be.a('object'); - expect(data).to.have.property('zone_id'); - expect(data.zone_id.split(';')).to.have.lengthOf(10); + data = new URLSearchParams(serverRequests[0].data); + expect(typeof data).to.equal('object'); + expect(data.get('zone_id')).to.be.exist; + expect(data.get('zone_id').split(';')).to.have.lengthOf(10); // TEST '100' BIDS, add 90 to the previously added 10 for (let i = 0; i < 90; i++) { @@ -1259,10 +1284,10 @@ describe('the rubicon adapter', function () { expect(serverRequests).that.is.an('array').of.length(1); // get the built query - let data = parseQuery(serverRequests[0].data); + let data = new URLSearchParams(serverRequests[0].data); // num slots should be 4 - expect(data.slots).to.equal('4'); + expect(data.get('slots')).to.equal('4'); }); it('should not group bid requests if singleRequest does not equal true', function () { @@ -1329,173 +1354,60 @@ describe('the rubicon adapter', 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 = [ - { - 'source': 'adserver.org', - 'uids': [ - { - 'id': 'abcd-efgh-ijkl-mnop-1234', - 'atype': 1, - 'ext': { - 'rtiPartner': 'TDID' - } - } - ] - } - ]; - 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 userIdAsEids contains liveintentId', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - lipb: { - lipbid: '0000-1111-2222-3333', - segments: ['segA', 'segB'] - } - }; - clonedBid.userIdAsEids = [ - { - 'source': 'liveintent.com', - 'uids': [ - { - 'id': '0000-1111-2222-3333', - 'atype': 3 - } - ], - 'ext': { - 'segments': [ - 'segA', - 'segB' - ] - } - } - ]; - 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 userIdAsEids contains liveintentId with ext.segments as array', function () { - const clonedBid = utils.deepClone(bidderRequest.bids[0]); - clonedBid.userId = { - lipb: { - lipbid: '1111-2222-3333-4444', - segments: ['segD', 'segE'] - } - }; - clonedBid.userIdAsEids = [ - { - 'source': 'liveintent.com', - 'uids': [ - { - 'id': '1111-2222-3333-4444', - 'atype': 3 - } - ], - 'ext': { - 'segments': [ - 'segD', - 'segE' - ] - } - } - ] - let [request] = spec.buildRequests([clonedBid], bidderRequest); - const unescapedData = unescape(request.data); - - expect(unescapedData.indexOf('&tpid_liveintent.com=1111-2222-3333-4444&') !== -1).to.equal(true); - expect(unescapedData.indexOf('&tg_v.LIseg=segD,segE&') !== -1).to.equal(true); - }); - }); - - describe('LiveRamp support', 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 = [ - { - 'source': 'liveramp.com', - 'uids': [ - { - 'id': '1111-2222-3333-4444', - 'atype': 3 - } - ] - } - ] - let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); - - 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 () { + it('should send eid_pubcid.org when ortb2.user.ext.eids contains pubcid', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { pubcid: '1111' }; - clonedBid.userIdAsEids = [ - { - 'source': 'pubcid.org', - 'uids': [ - { - 'id': '1111', - 'atype': 1 - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'pubcid.org', + 'uids': [{ + 'id': '1111', + 'atype': 1 + }] + }] + } } - ] + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_pubcid.org']).to.equal('1111^1'); + expect(data.get('eid_pubcid.org')).to.equal('1111^1^^^^^'); }); }); describe('Criteo support', function () { - it('should send eid_criteo.com when userIdAsEids contains criteo', function () { + it('should send eid_criteo.com when ortb2.user.ext.eids contains criteo', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { criteoId: '1111' }; - clonedBid.userIdAsEids = [ - { - 'source': 'criteo.com', - 'uids': [ - { - 'id': '1111', - 'atype': 1 - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': '1111', + 'atype': 1 + }] + }] + } } - ] + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_criteo.com']).to.equal('1111^1'); + expect(data.get('eid_criteo.com')).to.equal('1111^1^^^^^'); }); }); describe('pubProvidedId support', function () { - it('should send pubProvidedId when userIdAsEids contains pubProvidedId ids', function () { + it('should send pubProvidedId when ortb2.user.ext.eids contains pubProvidedId ids', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { pubProvidedId: [{ @@ -1513,36 +1425,36 @@ describe('the rubicon adapter', function () { }] }] }; - clonedBid.userIdAsEids = [ - { - 'source': 'example.com', - 'uids': [ - { - 'id': '11111', - 'ext': { - 'stype': 'ppuid' - } - } - ] - }, - { - 'source': 'id-partner.com', - 'uids': [ + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'example.com', + 'uids': [{ + 'id': '11111', + 'ext': { + 'stype': 'ppuid' + } + }] + }, { - 'id': '222222' - } - ] + 'source': 'id-partner.com', + 'uids': [{ + 'id': '222222' + }] + }] + } } - ]; + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['ppuid']).to.equal('11111'); + expect(data.get('ppuid')).to.equal('11111'); }); }); describe('ID5 support', function () { - it('should send ID5 id when userIdAsEids contains ID5', function () { + it('should send ID5 id when ortb2.user.ext.eids contains ID5', function () { const clonedBid = utils.deepClone(bidderRequest.bids[0]); clonedBid.userId = { id5id: { @@ -1552,24 +1464,26 @@ describe('the rubicon adapter', function () { } } }; - clonedBid.userIdAsEids = [ - { - 'source': 'id5-sync.com', - 'uids': [ - { - 'id': '11111', - 'atype': 1, - 'ext': { - 'linkType': '22222' - } - } - ] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'id5-sync.com', + 'uids': [{ + 'id': '11111', + 'atype': 1, + 'ext': { + 'linkType': '22222' + } + }] + }] + } } - ]; + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_id5-sync.com']).to.equal('11111^1^22222'); + expect(data.get('eid_id5-sync.com')).to.equal('11111^1^^^^^'); }); }); @@ -1577,36 +1491,82 @@ describe('the rubicon adapter', 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 - }] - }] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + 'source': 'catchall', + 'uids': [{ + 'id': '11111', + 'atype': 2 + }] + }] + } + } + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_catchall']).to.equal('11111^2'); + expect(data.get('eid_catchall')).to.equal('11111^2^^^^^'); }); it('should send rubiconproject special case', 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: 'rubiconproject.com', - uids: [{ - id: 'some-cool-id', - atype: 3 - }] - }] + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'rubiconproject.com', + uids: [{ + id: 'some-cool-id', + atype: 3 + }] + }] + } + } + }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['eid_rubiconproject.com']).to.equal('some-cool-id'); + expect(data.get('eid_rubiconproject.com')).to.equal('some-cool-id^3^^^^^'); }); - }); + describe('Full eidValue format validation', function () { + it('should send complete eidValue in the format uid^atype^third^inserter^matcher^mm^rtipartner', function () { + const clonedBid = utils.deepClone(bidderRequest.bids[0]); + // Simulating a full EID object with multiple fields + clonedBid.ortb2 = { + user: { + ext: { + eids: [{ + source: 'example.com', + uids: [{ + id: '11111', // UID + atype: 2, // atype + ext: { + rtipartner: 'rtipartner123', // rtipartner + stype: 'ppuid' // stype + } + }], + inserter: 'inserter123', // inserter + matcher: 'matcher123', // matcher + mm: 'mm123' // mm + }] + } + } + }; + + let [request] = spec.buildRequests([clonedBid], bidderRequest); + let data = new URLSearchParams(request.data); + + // Expected format: uid^atype^third^inserter^matcher^mm^rtipartner + const expectedEidValue = '11111^2^^inserter123^matcher123^mm123^rtipartner123'; + // Check if the generated EID value matches the expected format + expect(data.get('eid_example.com')).to.equal(expectedEidValue); + }); + }); + }); describe('Config user.id support', function () { it('should send ppuid when config defines user.id', function () { config.setConfig({user: {id: '123'}}); @@ -1615,9 +1575,9 @@ describe('the rubicon adapter', function () { pubcid: '1111' }; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['ppuid']).to.equal('123'); + expect(data.get('ppuid')).to.equal('123'); }); }); }); @@ -1632,20 +1592,20 @@ describe('the rubicon adapter', 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); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot’')).to.be.null; }); 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); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot’')).to.be.null; }); it('should not send \"tg_i.pbadslot’\" if \"ortb2Imp.ext.data.pbadslot\" value is an empty string', function () { @@ -1658,10 +1618,10 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.pbadslot'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.null; }); it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { @@ -1674,11 +1634,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.pbadslot'); - expect(data['tg_i.pbadslot']).to.equal('abc'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.exist; + expect(data.get('tg_i.pbadslot')).to.equal('abc'); }); it('should send \"tg_i.pbadslot\" if \"ortb2Imp.ext.data.pbadslot\" value is a valid string', function () { @@ -1691,11 +1651,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(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'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.pbadslot')).to.be.exist; + expect(data.get('tg_i.pbadslot')).to.equal('/a/b/c'); }); it('should send gpid as p_gpid if valid', function () { @@ -1706,11 +1666,11 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('p_gpid'); - expect(data['p_gpid']).to.equal('/1233/sports&div1'); + expect(typeof data).to.equal('object'); + expect(data.get('p_gpid')).to.be.exist; + expect(data.get('p_gpid')).to.equal('/1233/sports&div1'); }); describe('Pass DSA signals', function() { @@ -1750,9 +1710,9 @@ describe('the rubicon adapter', function () { const expectedTransparency = 'testdomain.com~1'; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({ ...b, ortb2: ortb2Clone })), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should send dsaparams if \"ortb2.regs.ext.dsa.transparancy[0].params\"', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1764,9 +1724,9 @@ describe('the rubicon adapter', function () { const expectedTransparency = 'testdomain.com~1'; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should pass an empty transparency param if \"ortb2.regs.ext.dsa.transparency[0].params\" is empty', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1777,8 +1737,8 @@ describe('the rubicon adapter', function () { }]; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); - expect(data['dsatransparency']).to.be.undefined + const data = new URLSearchParams(request.data); + expect(data.get('dsatransparency')).to.be.null }) it('should send an empty transparency if \"ortb2.regs.ext.dsa.transparency[0].domain\" is empty', function() { const ortb2Clone = JSON.parse(JSON.stringify(ortb2)); @@ -1789,36 +1749,36 @@ describe('the rubicon adapter', function () { }]; const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data['dsatransparency']).to.be.undefined + expect(data.get('dsatransparency')).to.be.null }) it('should send dsa signals if \"ortb2.regs.ext.dsa\"', function() { const expectedTransparency = 'testdomain.com~1~~testdomain2.com~1_2' const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2})), bidderRequest) - const data = parseQuery(request.data); - - expect(data).to.be.an('Object'); - expect(data).to.have.property('dsarequired'); - expect(data).to.have.property('dsapubrender'); - expect(data).to.have.property('dsadatatopubs'); - expect(data).to.have.property('dsatransparency'); - - expect(data['dsarequired']).to.equal(ortb2.regs.ext.dsa.dsarequired.toString()); - expect(data['dsapubrender']).to.equal(ortb2.regs.ext.dsa.pubrender.toString()); - expect(data['dsadatatopubs']).to.equal(ortb2.regs.ext.dsa.datatopub.toString()); - expect(data['dsatransparency']).to.equal(expectedTransparency) + const data = new URLSearchParams(request.data); + + expect(typeof data).to.equal('object'); + expect(data.get('dsarequired')).to.be.exist; + expect(data.get('dsapubrender')).to.be.exist; + expect(data.get('dsadatatopubs')).to.be.exist; + expect(data.get('dsatransparency')).to.be.exist; + + expect(data.get('dsarequired')).to.equal(ortb2.regs.ext.dsa.dsarequired.toString()); + expect(data.get('dsapubrender')).to.equal(ortb2.regs.ext.dsa.pubrender.toString()); + expect(data.get('dsadatatopubs')).to.equal(ortb2.regs.ext.dsa.datatopub.toString()); + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) it('should return one transparency param', function() { const expectedTransparency = 'testdomain.com~1'; const ortb2Clone = deepClone(ortb2); ortb2Clone.regs.ext.dsa.transparency.pop() const [request] = spec.buildRequests(bidderRequest.bids.map((b) => ({...b, ortb2: ortb2Clone})), bidderRequest) - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('dsatransparency'); - expect(data['dsatransparency']).to.equal(expectedTransparency); + expect(typeof data).to.equal('object'); + expect(data.get('dsatransparency')).to.be.exist; + expect(data.get('dsatransparency')).to.equal(expectedTransparency); }) }) @@ -1837,12 +1797,12 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data['p_gpid']).to.equal('/1233/sports&div1'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.pbadslot']).to.equal('pb_slot'); + expect(typeof data).to.equal('object'); + expect(data.get('p_gpid')).to.equal('/1233/sports&div1'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.null; + expect(data.get('tg_i.pbadslot')).to.equal('pb_slot'); }); }); @@ -1856,20 +1816,20 @@ describe('the rubicon adapter', function () { 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); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code’')).to.be.null; }); 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); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code’'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code’')).to.be.null; }); it('should not send \"tg_i.dfp_ad_unit_code’\" if \"ortb2Imp.ext.data.adServer.adslot\" value is an empty string', function () { @@ -1884,10 +1844,10 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.null; }); it('should send NOT \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string but not gam', function () { @@ -1903,10 +1863,10 @@ describe('the rubicon adapter', function () { } const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.not.have.property('tg_i.dfp_ad_unit_code'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.but.null; }); it('should send \"tg_i.dfp_ad_unit_code\" if \"ortb2Imp.ext.data.adServer.adslot\" value is a valid string and name is gam', function () { @@ -1922,11 +1882,11 @@ describe('the rubicon adapter', function () { }; const [request] = spec.buildRequests(bidderRequest.bids, bidderRequest); - const data = parseQuery(request.data); + const data = new URLSearchParams(request.data); - expect(data).to.be.an('Object'); - expect(data).to.have.property('tg_i.dfp_ad_unit_code'); - expect(data['tg_i.dfp_ad_unit_code']).to.equal('/a/b/c'); + expect(typeof data).to.equal('object'); + expect(data.get('tg_i.dfp_ad_unit_code')).to.be.exist; + expect(data.get('tg_i.dfp_ad_unit_code')).to.equal('/a/b/c'); }); }); @@ -1995,11 +1955,11 @@ describe('the rubicon adapter', function () { // Build Fastlane call let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // Loop through expected values and if they do not match push an error const errors = Object.entries(expectedValues).reduce((accum, [key, val]) => { - if (data[key] !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) + if (data.get(key) !== val) accum.push(`${key} - expect: ${val} - got: ${data[key]}`) return accum; }, []); @@ -2029,24 +1989,24 @@ describe('the rubicon adapter', function () { // Build Fastlane request let [request] = spec.buildRequests(bidRequestSua.bids, bidRequestSua); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); // should show new names - expect(data.m_ch_model).to.equal('Suface Duo'); - expect(data.m_ch_mobile).to.equal('?1'); + expect(data.get('m_ch_model')).to.equal('Suface Duo'); + expect(data.get('m_ch_mobile')).to.equal('?1'); // should still send platform - expect(data.m_ch_platform).to.equal('macOS'); + expect(data.get('m_ch_platform')).to.equal('macOS'); // platform version not sent - expect(data).to.not.haveOwnProperty('m_ch_platform_ver'); + expect(data.get('m_ch_platform_ver')).to.be.null; // both ua and full_ver not sent because browsers not array - expect(data).to.not.haveOwnProperty('m_ch_ua'); - expect(data).to.not.haveOwnProperty('m_ch_full_ver'); + expect(data.get('m_ch_ua')).to.be.null; + expect(data.get('m_ch_full_ver')).to.be.null; // arch not sent - expect(data).to.not.haveOwnProperty('m_ch_arch'); + expect(data.get('m_ch_arch')).to.be.null; }); }); }); @@ -2868,6 +2828,36 @@ describe('the rubicon adapter', function () { expect(slotParams['tg_i.tax10']).is.equal('2,3'); expect(slotParams['tg_v.tax404']).is.equal(undefined); }); + + it('should add p_site.mobile if mobile is a number in ortb2.site', function () { + // Set up a bidRequest with mobile property as a number + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.bids[0].ortb2 = { + site: { + mobile: 1 // Valid mobile value (number) + } + }; + + // Call the function + const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest); + // Check that p_site.mobile was added to the slotParams with the correct value + expect(slotParams['p_site.mobile']).to.equal(1); + }); + it('should not add p_site.mobile if mobile is not a number in ortb2.site', function () { + // Set up a bidRequest with mobile property as a string (invalid value) + const localBidderRequest = Object.assign({}, bidderRequest); + localBidderRequest.bids[0].ortb2 = { + site: { + mobile: 'not-a-number' // Invalid mobile value (string) + } + }; + + // Call the function + const slotParams = spec.createSlotParams(localBidderRequest.bids[0], localBidderRequest); + + // Check that p_site.mobile is not added to the slotParams + expect(slotParams['p_site.mobile']).to.be.undefined; + }); }); describe('classifiedAsVideo', function () { @@ -3059,6 +3049,21 @@ describe('the rubicon adapter', function () { expect(other).to.be.empty; }); }); + + describe('with duplicate adUnitCodes', () => { + it('should increment PBS request imp[].id starting at 2', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest, {twin: true}); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + for (let i = 0; i < nativeBidderRequest.bids.length; i++) { + var adUnitCode = nativeBidderRequest.bids[i].adUnitCode; + if (i === 0) { + expect(request.imp[i].id).to.equal(adUnitCode); + } else { + expect(request.imp[i].id).to.equal(adUnitCode + (i + 1)); + } + } + }); + }); }); } }); @@ -3695,14 +3700,14 @@ describe('the rubicon adapter', function () { }] }; - let {bids, fledgeAuctionConfigs} = spec.interpretResponse({body: response}, { + let {bids, paapi} = spec.interpretResponse({body: response}, { bidRequest: bidderRequest.bids[0] }); expect(bids).to.be.lengthOf(1); - expect(fledgeAuctionConfigs[0].bidId).to.equal('5432'); - expect(fledgeAuctionConfigs[0].config.random).to.equal('value'); - expect(fledgeAuctionConfigs[1].bidId).to.equal('6789'); + expect(paapi[0].bidId).to.equal('5432'); + expect(paapi[0].config.random).to.equal('value'); + expect(paapi[1].bidId).to.equal('6789'); }); it('should handle an error', function () { @@ -3766,6 +3771,71 @@ describe('the rubicon adapter', function () { expect(bids[0].cpm).to.be.equal(0); }); + it('should use ads.emulated_format if defined for bid.meta.mediaType', function () { + 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, + 'emulated_format': 'video', + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '15_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': 0.911, + 'targeting': [ + { + 'key': 'rpfl_14062', + 'values': [ + '43_tier_all_test' + ] + } + ] + } + ] + }; + let bids = spec.interpretResponse({body: response}, { + bidRequest: bidderRequest.bids[0] + }); + expect(bids[0].meta.mediaType).to.equal('banner'); + expect(bids[1].meta.mediaType).to.equal('video'); + }); + describe('singleRequest enabled', function () { it('handles bidRequest of type Array and returns associated adUnits', function () { const overrideMap = []; @@ -3987,6 +4057,16 @@ describe('the rubicon adapter', function () { let bids = spec.interpretResponse({body: response}, {data: request}); expect(bids).to.have.nested.property('[0].native'); }); + it('should set 0 to bids width and height if `w` and `h` in response object not defined', () => { + const nativeBidderRequest = addNativeToBidRequest(bidderRequest); + const request = converter.toORTB({bidderRequest: nativeBidderRequest, bidRequests: nativeBidderRequest.bids}); + let response = getNativeResponse({impid: request.imp[0].id}); + delete response.seatbid[0].bid[0].w; + delete response.seatbid[0].bid[0].h + let bids = spec.interpretResponse({body: response}, {data: request}); + expect(bids[0].width).to.equal(0); + expect(bids[0].height).to.equal(0); + }); }); } @@ -4117,6 +4197,7 @@ describe('the rubicon adapter', function () { const bid = bids[0]; bid.adUnitCode = 'outstream_video1_placement'; const adUnit = document.createElement('div'); + const adUnitSelector = `#${bid.adUnitCode}` adUnit.id = bid.adUnitCode; document.body.appendChild(adUnit); @@ -4130,7 +4211,7 @@ describe('the rubicon adapter', function () { label: undefined, placement: { align: 'left', - attachTo: adUnit, + attachTo: adUnitSelector, position: 'append', }, vastUrl: 'https://test.com/vast.xml', @@ -4186,6 +4267,7 @@ describe('the rubicon adapter', function () { const bid = bids[0]; bid.adUnitCode = 'outstream_video1_placement'; const adUnit = document.createElement('div'); + const adUnitSelector = `#${bid.adUnitCode}` adUnit.id = bid.adUnitCode; document.body.appendChild(adUnit); @@ -4199,7 +4281,7 @@ describe('the rubicon adapter', function () { label: undefined, placement: { align: 'left', - attachTo: adUnit, + attachTo: adUnitSelector, position: 'append', }, vastUrl: 'https://test.com/vast.xml', @@ -4215,7 +4297,7 @@ describe('the rubicon adapter', function () { it('should use the integration type provided in the config instead of the default', () => { 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$'); + expect(new URLSearchParams(request.data).get('tk_flint')).to.equal('testType_v$prebid.version$'); }); }); }); @@ -4438,7 +4520,7 @@ describe('the rubicon adapter', function () { it('should properly serialize schain object with correct delimiters', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); const numNodes = schainConfig.nodes.length; - const schain = parseQuery(results[0].data).rp_schain; + const schain = new URLSearchParams(results[0].data).get('rp_schain'); // each node serialization should start with an ! expect(schain.match(/!/g).length).to.equal(numNodes); @@ -4449,21 +4531,21 @@ describe('the rubicon adapter', function () { it('should send the proper version for the schain', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); const version = schain.shift().split(',')[0]; expect(version).to.equal(bidRequests.bids[0].schain.ver); }); it('should send the correct value for complete in schain', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); const complete = schain.shift().split(',')[1]; expect(complete).to.equal(String(bidRequests.bids[0].schain.complete)); }); it('should send available params in the right order', () => { const results = spec.buildRequests(bidRequests.bids, bidRequests); - const schain = parseQuery(results[0].data).rp_schain.split('!'); + const schain = new URLSearchParams(results[0].data).get('rp_schain').split('!'); schain.shift(); schain.forEach((serializeNode, nodeIndex) => { @@ -4534,7 +4616,7 @@ describe('the rubicon adapter', function () { }); }); -function addNativeToBidRequest(bidderRequest) { +function addNativeToBidRequest(bidderRequest, options = {twin: false}) { const nativeOrtbRequest = { assets: [{ id: 0, @@ -4563,27 +4645,30 @@ function addNativeToBidRequest(bidderRequest) { bidderRequest.refererInfo = { page: 'localhost' } - bidderRequest.bids[0] = { - bidder: 'rubicon', - params: { - accountId: '14062', - siteId: '70608', - zoneId: '335918', - }, - adUnitCode: '/19968336/header-bid-tag-0', - code: 'div-1', - bidId: '2ffb201a808da7', - bidderRequestId: '178e34bad3658f', - auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', - transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', - mediaTypes: { - native: { - ortb: { - ...nativeOrtbRequest + const numBids = !options.twin ? 1 : 2; + for (let i = 0; i < numBids; i++) { + bidderRequest.bids[i] = { + bidder: 'rubicon', + params: { + accountId: '14062', + siteId: '70608', + zoneId: '335918', + }, + adUnitCode: '/19968336/header-bid-tag-0', + code: 'div-1', + bidId: '2ffb201a808da7', + bidderRequestId: '178e34bad3658f', + auctionId: 'c45dd708-a418-42ec-b8a7-b70a6c6fab0a', + transactionId: 'd45dd707-a418-42ec-b8a7-b70a6c6fab0b', + mediaTypes: { + native: { + ortb: { + ...nativeOrtbRequest + } } - } - }, - nativeOrtbRequest + }, + nativeOrtbRequest + } } return bidderRequest; } diff --git a/test/spec/modules/seedingAllianceAdapter_spec.js b/test/spec/modules/seedingAllianceAdapter_spec.js index 03548cf923a..e3ff85e9c83 100755 --- a/test/spec/modules/seedingAllianceAdapter_spec.js +++ b/test/spec/modules/seedingAllianceAdapter_spec.js @@ -1,8 +1,7 @@ // jshint esversion: 6, es3: false, node: true import {assert, expect} from 'chai'; +import {getStorageManager} from 'src/storageManager.js'; 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; @@ -13,6 +12,14 @@ describe('SeedingAlliance adapter', function () { } }; + let validBidRequests = [{ + bidId: 'bidId', + params: {}, + mediaType: { + native: {} + } + }]; + describe('isBidRequestValid', function () { it('should return true when required params found', function () { assert(spec.isBidRequestValid(bid)); @@ -26,11 +33,6 @@ describe('SeedingAlliance adapter', function () { 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'); @@ -39,64 +41,96 @@ describe('SeedingAlliance adapter', function () { it('should have default request structure', function () { let keys = 'site,cur,imp,regs'.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); + assert.includeDeepMembers(data, keys); }); it('Verify the site url', function () { let siteUrl = 'https://www.yourdomain.tld/your-directory/'; - let validBidRequests = [{ - bidId: 'bidId', - params: { - url: siteUrl - } - }]; + validBidRequests[0].params.url = siteUrl; let request = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data); assert.equal(request.site.page, siteUrl); }); + }); - 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 - } + describe('check user ID functionality', function () { + let storage = getStorageManager({ bidderCode: 'seedingAlliance' }); + let localStorageIsEnabledStub = sinon.stub(storage, 'localStorageIsEnabled'); + let getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); + const bidRequests = [{ + bidId: 'bidId', + params: {} + }]; + const bidderRequest = { + refererInfo: { referer: 'page' }, + gdprConsent: 'CP0j9IAP0j9IAAGABCENAYEgAP_gAAAAAAYgIxBVBCpNDWFAMHBVAJIgCYAU1sARIAQAABCAAyAFAAOA8IAA0QECEAQAAAACAAAAgVABAAAAAABEAACAAAAEAQFkAAQQgAAIAAAAAAEQQgBQAAgAAAAAEAAIgAABAwQAkACQIYLEBUCAhIAgCgAAAIgBgICAAgMACEAYAAAAAAIAAIBAAgIEMIAAAAECAQAAAFhIEoACAAKgAcgA-AEAAMgAaABEACYAG8APwAhIBDAESAJYATQAw4B9gH6ARQAjQBKQC5gF6AMUAbQA3ACdgFDgLzAYMAw0BmYDVwGsgOCAcmA8cCEMELQQuCAAgGQgQMHQKAAKgAcgA-AEAAMgAaABEACYAG8AP0AhgCJAEsAJoAYYA0YB9gH6ARQAiwBIgCUgFzAL0AYoA2gBuAEXgJkATsAocBeYDBgGGgMqAZYAzMBpoDVwHFgOTAeOBC0cAHAAQABcAKACEAF0AMEAZCQgFABMADeARQAlIBcwDFAG0AeOBCgCFpAAGAAgBggEMyUAwABAAHAAPgBEACZAIYAiQB-AFzAMUAi8BeYEISQAMAC4DLAIZlIEAAFQAOQAfACAAGQANAAiABMACkAH6AQwBEgDRgH4AfoBFgCRAEpALmAYoA2gBuAEXgJ2AUOAvMBhoDLAGsgOCAcmA8cCEIELQIZlAAoAFwB9gLoAYIBAwtADAL0AzMB44AAA.f_wAAAAAAAAA' + } + let request; + + before(function () { + storage.removeDataFromLocalStorage('nativendo_id'); + const localStorageData = { + nativendo_id: '123' + }; + + getDataFromLocalStorageStub.callsFake(function (key) { + return localStorageData[key]; + }); + }); + + it('should return an empty array if local storage is not enabled', function () { + localStorageIsEnabledStub.returns(false); + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: false } - }]; + }; + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.empty; + }); + + it('should return an empty array if local storage is enabled but storageAllowed is false', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: false + } + }; + localStorageIsEnabledStub.returns(true); + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.empty; + }); + + it('should return a non empty array if local storage is enabled and storageAllowed is true', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: true + } + }; + localStorageIsEnabledStub.returns(true); + + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); + expect(request.user.ext.eids).to.be.an('array').that.is.not.empty; + }); + + it('should return an array containing the nativendoUserEid', function () { + $$PREBID_GLOBAL$$.bidderSettings = { + seedingAlliance: { + storageAllowed: true + } + }; + localStorageIsEnabledStub.returns(true); + + let nativendoUserEid = { source: 'nativendo.de', uids: [{ id: '123', atype: 1 }] }; + storage.setDataInLocalStorage('nativendo_id', '123'); - let assets = JSON.parse(spec.buildRequests(validBidRequests, { refererInfo: { referer: 'page' } }).data).imp[0].native.request.assets; + request = JSON.parse(spec.buildRequests(bidRequests, bidderRequest).data); - 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); + expect(request.user.ext.eids).to.deep.include(nativendoUserEid); }); }); @@ -104,23 +138,24 @@ describe('SeedingAlliance adapter', function () { const goodNativeResponse = { body: { cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', 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/' - } - } - }, + adm: JSON.stringify({ + native: { + assets: [ + {id: 0, title: {text: 'this is a title'}}, + {id: 1, img: {url: 'https://domain.for/img.jpg'}}, + ], + 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 }] @@ -132,7 +167,7 @@ describe('SeedingAlliance adapter', function () { const goodBannerResponse = { body: { cur: 'EUR', - id: 'b4516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', seatbid: [ { seat: 'seedingAlliance', @@ -150,18 +185,18 @@ describe('SeedingAlliance adapter', function () { const badResponse = { body: { cur: 'EUR', - id: '4b516b80-886e-4ec0-82ae-9209e6d625fb', + id: 'bidid1', seatbid: [] }}; const bidNativeRequest = { data: {}, - bidRequests: [{bidId: 'bidId1', nativeParams: {title: {required: true, len: 800}}}] + bidRequests: [{bidId: '1', nativeParams: {title: {required: true, len: 800}, image: {required: true, sizes: [300, 250]}}}] }; const bidBannerRequest = { data: {}, - bidRequests: [{bidId: 'bidId1', sizes: [300, 250]}] + bidRequests: [{bidId: '1', sizes: [300, 250]}] }; it('should return null if body is missing or empty', function () { diff --git a/test/spec/modules/seedtagBidAdapter_spec.js b/test/spec/modules/seedtagBidAdapter_spec.js index 953d9ff32ef..db19d71f23f 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -3,7 +3,7 @@ import { getTimeoutUrl, spec } from 'modules/seedtagBidAdapter.js'; import * as utils from 'src/utils.js'; import * as mockGpt from 'test/spec/integration/faker/googletag.js'; import { config } from '../../../src/config.js'; -import * as mockGpt from 'test/spec/integration/faker/googletag.js'; +import { BIDFLOOR_CURRENCY } from '../../../modules/seedtagBidAdapter.js'; const PUBLISHER_ID = '0000-0000-01'; const ADUNIT_ID = '000000'; @@ -38,6 +38,7 @@ function getSlotConfigs(mediaTypes, params) { ortb2Imp: { ext: { tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + gpid: 'some-gpid' } }, adUnitCode: adUnitCode, @@ -48,15 +49,13 @@ function createInStreamSlotConfig(mediaType) { return getSlotConfigs(mediaType, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'inStream', }); } -const createBannerSlotConfig = (placement, mediatypes) => { +const createBannerSlotConfig = (mediatypes) => { return getSlotConfigs(mediatypes || { banner: {} }, { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement, }); }; @@ -71,49 +70,69 @@ describe('Seedtag Adapter', function () { describe('isBidRequestValid method', function () { describe('returns true', function () { describe('when banner slot config has all mandatory params', () => { - const placements = ['inBanner', 'inImage', 'inScreen', 'inArticle']; - placements.forEach((placement) => { - it(placement + 'should be valid', function () { + it('should be valid', function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig() + ); + expect(isBidRequestValid).to.equal(true); + }); + + it('should be valid when has display and video mediatypes, and video context is outstream', + function () { const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement) + createBannerSlotConfig({ + banner: {}, + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) ); expect(isBidRequestValid).to.equal(true); - }); - - it( - placement + - ' should be valid when has display and video mediatypes, and video context is outstream', - function () { - const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement, { - banner: {}, - video: { - context: 'outstream', - playerSize: [[600, 200]], - }, - }) - ); - expect(isBidRequestValid).to.equal(true); - } - ); + } + ); - it( - placement + - " shouldn't be valid when has display and video mediatypes, and video context is instream", - function () { - const isBidRequestValid = spec.isBidRequestValid( - createBannerSlotConfig(placement, { - banner: {}, - video: { - context: 'instream', - playerSize: [[600, 200]], - }, - }) - ); - expect(isBidRequestValid).to.equal(false); - } - ); - }); + it('should be valid when has only video mediatypes, and video context is outstream', + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig({ + video: { + context: 'outstream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(true); + } + ); + it('should be valid when has display and video mediatypes, and video context is instream', + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig({ + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); + it("shouldn't be valid when has display and video mediatypes, and video context is instream", + function () { + const isBidRequestValid = spec.isBidRequestValid( + createBannerSlotConfig({ + banner: {}, + video: { + context: 'instream', + playerSize: [[600, 200]], + }, + }) + ); + expect(isBidRequestValid).to.equal(false); + } + ); }); describe('when video slot has all mandatory params', function () { it('should return true, when video context is instream', function () { @@ -126,7 +145,7 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(true); }); - it('should return true, when video context is instream and mediatype is video and banner', function () { + it('should return false, when video context is instream and mediatype is video and banner', function () { const slotConfig = createInStreamSlotConfig({ video: { context: 'instream', @@ -135,33 +154,6 @@ describe('Seedtag Adapter', function () { banner: {}, }); const isBidRequestValid = spec.isBidRequestValid(slotConfig); - expect(isBidRequestValid).to.equal(true); - }); - it('should return false, when video context is instream, but placement is not inStream', function () { - const slotConfig = getSlotConfigs( - { - video: { - context: 'instream', - playerSize: [[600, 200]], - }, - }, - { - publisherId: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement: 'inBanner', - } - ); - const isBidRequestValid = spec.isBidRequestValid(slotConfig); - expect(isBidRequestValid).to.equal(false); - }); - it('should return false, when video context is outstream', function () { - const slotConfig = createInStreamSlotConfig({ - video: { - context: 'outstream', - playerSize: [[600, 200]], - }, - }); - const isBidRequestValid = spec.isBidRequestValid(slotConfig); expect(isBidRequestValid).to.equal(false); }); }); @@ -175,7 +167,6 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ adUnitId: ADUNIT_ID, - placement: 'inBanner', }) ); expect(isBidRequestValid).to.equal(false); @@ -184,26 +175,6 @@ describe('Seedtag Adapter', function () { const isBidRequestValid = spec.isBidRequestValid( createSlotConfig({ publisherId: PUBLISHER_ID, - placement: 'inBanner', - }) - ); - expect(isBidRequestValid).to.equal(false); - }); - it('does not have the placement.', function () { - const isBidRequestValid = spec.isBidRequestValid( - 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: PUBLISHER_ID, - adUnitId: ADUNIT_ID, - placement: 'another_thing', }) ); expect(isBidRequestValid).to.equal(false); @@ -223,17 +194,7 @@ describe('Seedtag Adapter', function () { ); expect(isBidRequestValid).to.equal(false); }); - it('is outstream ', function () { - const isBidRequestValid = spec.isBidRequestValid( - createInStreamSlotConfig({ - video: { - context: 'outstream', - playerSize: [[600, 200]], - }, - }) - ); - expect(isBidRequestValid).to.equal(false); - }); + describe('order does not matter', function () { it('when video is not the first slot', function () { const isBidRequestValid = spec.isBidRequestValid( @@ -253,6 +214,7 @@ describe('Seedtag Adapter', function () { }); describe('buildRequests method', function () { + const bidFloor = 0.60 const bidderRequest = { refererInfo: { page: 'referer' }, timeout: 1000, @@ -260,12 +222,10 @@ describe('Seedtag Adapter', function () { const mandatoryDisplayParams = { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'inBanner', }; const mandatoryVideoParams = { publisherId: PUBLISHER_ID, adUnitId: ADUNIT_ID, - placement: 'inStream', }; const validBidRequests = [ getSlotConfigs({ banner: {} }, mandatoryDisplayParams), @@ -280,6 +240,11 @@ describe('Seedtag Adapter', function () { mandatoryVideoParams ), ]; + validBidRequests[0].getFloor = () => ({ + currency: BIDFLOOR_CURRENCY, + floor: bidFloor + }) + it('Url params should be correct ', function () { const request = spec.buildRequests(validBidRequests, bidderRequest); expect(request.method).to.equal('POST'); @@ -300,6 +265,7 @@ describe('Seedtag Adapter', function () { expect(data.ttfb).to.be.greaterThanOrEqual(0); expect(data.bidRequests[0].adUnitCode).to.equal(adUnitCode); + expect(data.bidRequests[0].gpid).to.equal('some-gpid'); }); describe('GDPR params', function () { @@ -425,6 +391,15 @@ describe('Seedtag Adapter', function () { expect(bannerBid).to.not.have.property('geom') } }) + + it('should have bidfloor parameter if available', function() { + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + const bidRequests = data.bidRequests; + + expect(bidRequests[0].bidFloor).to.be.equal(bidFloor) + expect(bidRequests[1]).not.to.have.property('bidFloor') + }) }); describe('COPPA param', function () { @@ -627,6 +602,88 @@ describe('Seedtag Adapter', function () { expect(data.badv).to.be.undefined; }); }); + + describe('Site params', function () { + it('should add cat param to payload when bidderRequest has ortb2 site cat info', function () { + const siteCategories = ['1217', 'bsr004', '692'] + var ortb2 = { + site: { + cat: siteCategories + } + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.site.cat).to.deep.equal(siteCategories); + }); + + it('should add pagecat param to payload when bidderRequest has ortb2 site pagecat info', function () { + const pageCategories = ['1217', 'bsr004', '692'] + var ortb2 = { + site: { + pagecat: pageCategories + } + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.site.pagecat).to.deep.equal(pageCategories); + }); + + it('should add cattac param to payload when bidderRequest has ortb2 site cattax info', function () { + const taxonomy = 6 + var ortb2 = { + site: { + cattax: taxonomy + } + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.site.cattax).to.equal(taxonomy); + }); + + it('should not add site params to payload when bidderRequest does not have ortb2 site info', function () { + var ortb2 = {} + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.site.cattax).to.be.undefined; + expect(data.site.cat).to.be.undefined; + expect(data.site.pagecat).to.be.undefined; + }); + }); + + describe('device.sua param', function () { + it('should add device.sua param to payload when bidderRequest has ortb2 device.sua info', function () { + const sua = 1 + var ortb2 = { + device: { + sua: sua + } + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.sua).to.equal(sua); + }); + + it('should not add device.sua param to payload when bidderRequest does not have ortb2 device.sua info', function () { + var ortb2 = { + device: {} + } + bidderRequest['ortb2'] = ortb2 + + const request = spec.buildRequests(validBidRequests, bidderRequest); + const data = JSON.parse(request.data); + expect(data.sua).to.be.undefined; + }); + }); }) describe('interpret response method', function () { it('should return a void array, when the server response are not correct.', function () { diff --git a/test/spec/modules/setupadBidAdapter_spec.js b/test/spec/modules/setupadBidAdapter_spec.js index d4ff73d005f..3a184c50922 100644 --- a/test/spec/modules/setupadBidAdapter_spec.js +++ b/test/spec/modules/setupadBidAdapter_spec.js @@ -1,4 +1,4 @@ -import { spec } from 'modules/setupadBidAdapter.js'; +import { spec, biddersCreativeIds } from 'modules/setupadBidAdapter.js'; describe('SetupadAdapter', function () { const userIdAsEids = [ @@ -42,9 +42,104 @@ describe('SetupadAdapter', function () { }, userIdAsEids, }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, ]; const bidderRequest = { + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + auctionStart: 1579746300522, + bidderCode: 'setupad', + bidderRequestId: '15246a574e859f', + bids: [ + { + adUnitCode: 'test-div', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + { + adUnitCode: 'test-div-2', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidId: '22c4871113f461', + bidder: 'rubicon', + bidderRequestId: '15246a574e859f', + uspConsent: 'usp-context-string', + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + gdprApplies: true, + }, + params: { + placement_id: '123', + account_id: 'test-account-id', + }, + sizes: [[300, 250]], + ortb2: { + device: { + w: 1500, + h: 1000, + }, + site: { + domain: 'test.com', + page: 'http://test.com', + }, + }, + userIdAsEids, + }, + ], + gdprConsent: { + consentString: 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA', + vendorData: {}, + gdprApplies: true, + }, ortb2: { device: { w: 1500, @@ -52,39 +147,27 @@ describe('SetupadAdapter', function () { }, }, refererInfo: { + canonicalUrl: null, domain: 'test.com', page: 'http://test.com', - ref: '', + referer: null, }, }; const serverResponse = { body: { - id: 'f7b3d2da-e762-410c-b069-424f92c4c4b2', seatbid: [ { - bid: [ - { - id: 'test-bid-id', - price: 0.8, - adm: 'this is an ad', - adid: 'test-ad-id', - adomain: ['test.addomain.com'], - w: 300, - h: 250, - }, - ], - seat: 'testBidder', + bid: [{ crid: 123 }, { crid: 1234 }], + seat: 'pubmatic', }, - ], - cur: 'USD', - ext: { - sync: { - image: ['urlA?gdpr={{.GDPR}}'], - iframe: ['urlB'], + { + bid: [{ crid: 12345 }], + seat: 'setupad', }, - }, + ], }, + testCase: 1, }; describe('isBidRequestValid', function () { @@ -92,11 +175,26 @@ describe('SetupadAdapter', function () { bidder: 'setupad', params: { placement_id: '123', + account_id: '123', }, }; + it('should return true when required params found', function () { expect(spec.isBidRequestValid(bid)).to.equal(true); }); + + it('should return false when placement_id is missing', function () { + const bidWithoutPlacementId = { ...bid }; + delete bidWithoutPlacementId.params.placement_id; + expect(spec.isBidRequestValid(bidWithoutPlacementId)).to.equal(false); + }); + + it('should return false when account_id is missing', function () { + const bidWithoutAccountId = { ...bid }; + delete bidWithoutAccountId.params.account_id; + expect(spec.isBidRequestValid(bidWithoutAccountId)).to.equal(false); + }); + it('should return false when required params are not passed', function () { delete bid.params.placement_id; expect(spec.isBidRequestValid(bid)).to.equal(false); @@ -104,77 +202,25 @@ describe('SetupadAdapter', function () { }); describe('buildRequests', function () { - it('check request params with GDPR and USP', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).user.ext.consent).to.equal( - 'BOtmiBKOtmiBKABABAENAFAAAAACeAAA' - ); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.equal(1); - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); - }); - - it('check request params without GDPR', function () { - let bidRequestsWithoutGDPR = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutGDPR.gdprConsent; - const request = spec.buildRequests([bidRequestsWithoutGDPR], bidRequestsWithoutGDPR); - expect(JSON.parse(request[0].data).regs.ext.gdpr).to.be.undefined; - expect(JSON.parse(request[0].data).regs.ext.us_privacy).to.equal('usp-context-string'); + it('should return correct storedrequest id for bids if placement_id is provided', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.imp[0].ext.prebid.storedrequest.id).to.equal('123'); }); it('should return correct storedrequest id if account_id is provided', function () { - const request = spec.buildRequests(bidRequests, bidRequests[0]); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('test-account-id'); - }); - - it('should return correct storedrequest id if account_id is not provided', function () { - let bidRequestsWithoutAccountId = Object.assign({}, bidRequests[0]); - delete bidRequestsWithoutAccountId.params.account_id; - const request = spec.buildRequests( - [bidRequestsWithoutAccountId], - bidRequestsWithoutAccountId - ); - expect(JSON.parse(request[0].data).ext.prebid.storedrequest.id).to.equal('default'); - }); - - it('validate generated params', function () { - const request = spec.buildRequests(bidRequests); - expect(request[0].bidId).to.equal('22c4871113f461'); - expect(JSON.parse(request[0].data).id).to.equal('15246a574e859f'); - }); - - it('check if correct site object was added', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const siteObj = JSON.parse(request[0].data).site; - - expect(siteObj.domain).to.equal('test.com'); - expect(siteObj.page).to.equal('http://test.com'); - expect(siteObj.ref).to.equal(''); + expect(request.data.ext.prebid.storedrequest.id).to.equal('test-account-id'); }); - it('check if correct device object was added', function () { + it('should return setupad custom adapter param', function () { const request = spec.buildRequests(bidRequests, bidderRequest); - const deviceObj = JSON.parse(request[0].data).device; - - expect(deviceObj.w).to.equal(1500); - expect(deviceObj.h).to.equal(1000); - }); - - it('check if imp object was added', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).imp).to.be.an('array'); - }); - - it('should send "user.ext.eids" in the request for Prebid.js supported modules only', function () { - const request = spec.buildRequests(bidRequests); - expect(JSON.parse(request[0].data).user.ext.eids).to.deep.equal(userIdAsEids); + expect(request.data.setupad).to.equal('adapter'); }); - it('should send an undefined "user.ext.eids" in the request if userId module is unsupported', function () { - let bidRequestsUnsupportedUserIdModule = Object.assign({}, bidRequests[0]); - delete bidRequestsUnsupportedUserIdModule.userIdAsEids; - const request = spec.buildRequests(bidRequestsUnsupportedUserIdModule); - - expect(JSON.parse(request[0].data).user.ext.eids).to.be.undefined; + // Change this to 1 whenever TEST_REQUEST = 1. This is allowed only for testing requests locally + it('should return correct test attribute value from global value', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.test).to.equal(0); }); }); @@ -241,25 +287,21 @@ describe('SetupadAdapter', function () { describe('interpretResponse', function () { it('should return empty array if error during parsing', () => { const wrongServerResponse = 'wrong data'; - let request = spec.buildRequests(bidRequests, bidRequests[0]); + let request = spec.buildRequests(bidRequests, bidderRequest); let result = spec.interpretResponse(wrongServerResponse, request); expect(result).to.be.instanceof(Array); expect(result.length).to.equal(0); }); - it('should get correct bid response', function () { - const result = spec.interpretResponse(serverResponse, bidRequests[0]); - expect(result).to.be.an('array').with.lengthOf(1); - expect(result[0].requestId).to.equal('22c4871113f461'); - expect(result[0].cpm).to.equal(0.8); - expect(result[0].width).to.equal(300); - expect(result[0].height).to.equal(250); - expect(result[0].creativeId).to.equal('test-bid-id'); - expect(result[0].currency).to.equal('USD'); - expect(result[0].netRevenue).to.equal(true); - expect(result[0].ttl).to.equal(360); - expect(result[0].ad).to.equal('this is an ad'); + it('should update biddersCreativeIds correctly', function () { + spec.interpretResponse(serverResponse, bidderRequest); + + expect(biddersCreativeIds).to.deep.equal({ + 123: 'pubmatic', + 1234: 'pubmatic', + 12345: 'setupad', + }); }); }); diff --git a/test/spec/modules/sharedIdSystem_spec.js b/test/spec/modules/sharedIdSystem_spec.js index fcfbe5f7c3f..359cbeb4651 100644 --- a/test/spec/modules/sharedIdSystem_spec.js +++ b/test/spec/modules/sharedIdSystem_spec.js @@ -3,6 +3,8 @@ import {coppaDataHandler} from 'src/adapterManager'; import sinon from 'sinon'; import * as utils from 'src/utils.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; let expect = require('chai').expect; @@ -91,4 +93,20 @@ describe('SharedId System', function () { expect(result).to.be.undefined; }); }); + describe('eid', () => { + before(() => { + attachIdSystem(sharedIdSystemSubmodule); + }); + it('pubCommonId', function() { + const userId = { + pubcid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'pubcid.org', + uids: [{id: 'some-random-id-value', atype: 1}] + }); + }); + }) }); diff --git a/test/spec/modules/sharethroughBidAdapter_spec.js b/test/spec/modules/sharethroughBidAdapter_spec.js index ab099d87429..0e0bc7fd14c 100644 --- a/test/spec/modules/sharethroughBidAdapter_spec.js +++ b/test/spec/modules/sharethroughBidAdapter_spec.js @@ -4,6 +4,7 @@ import * as sinon from 'sinon'; import { newBidder } from 'src/adapters/bidderFactory.js'; import { config } from 'src/config'; import * as utils from 'src/utils'; +import { deepSetValue } from '../../../src/utils'; const spec = newBidder(sharethroughAdapterSpec).getSpec(); @@ -38,19 +39,8 @@ describe('sharethrough adapter spec', function () { expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); }); - it('should return false if req has wrong bidder code', function () { - const invalidBidRequest = { - bidder: 'notSharethrough', - params: { - pkey: 'abc123', - }, - }; - expect(spec.isBidRequestValid(invalidBidRequest)).to.eql(false); - }); - it('should return true if req is correct', function () { const validBidRequest = { - bidder: 'sharethrough', params: { pkey: 'abc123', }, @@ -85,6 +75,7 @@ describe('sharethrough adapter spec', function () { mediaTypes: { banner: { pos: 1, + battr: [6, 7], }, }, ortb2Imp: { @@ -226,7 +217,7 @@ describe('sharethrough adapter spec', function () { video: { pos: 3, skip: 1, - linearity: 0, + linearity: 1, minduration: 10, maxduration: 30, playbackmethod: [1], @@ -238,9 +229,12 @@ describe('sharethrough adapter spec', function () { skipmin: 10, skipafter: 20, delivery: 1, + battr: [13, 14], companiontype: 'companion type', companionad: 'companion ad', context: 'instream', + placement: 1, + plcmt: 1, }, }, getFloor: () => ({ currency: 'USD', floor: 42 }), @@ -346,6 +340,41 @@ describe('sharethrough adapter spec', function () { expect(openRtbReq.user.ext.eids).to.deep.equal([]); }); + + it('should add ORTB2 device data to the request', () => { + const bidderRequestWithOrtb2Device = { + ...bidderRequest, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }, + }; + + const [request] = spec.buildRequests(bidRequests, bidderRequestWithOrtb2Device); + + expect(request.data.device.w).to.equal(bidderRequestWithOrtb2Device.ortb2.device.w); + expect(request.data.device.h).to.equal(bidderRequestWithOrtb2Device.ortb2.device.h); + expect(request.data.device.dnt).to.equal(bidderRequestWithOrtb2Device.ortb2.device.dnt); + expect(request.data.device.ua).to.equal(bidderRequestWithOrtb2Device.ortb2.device.ua); + expect(request.data.device.language).to.equal(bidderRequestWithOrtb2Device.ortb2.device.language); + expect(request.data.device.devicetype).to.equal(bidderRequestWithOrtb2Device.ortb2.device.devicetype); + expect(request.data.device.make).to.equal(bidderRequestWithOrtb2Device.ortb2.device.make); + expect(request.data.device.model).to.equal(bidderRequestWithOrtb2Device.ortb2.device.model); + expect(request.data.device.os).to.equal(bidderRequestWithOrtb2Device.ortb2.device.os); + expect(request.data.device.osv).to.equal(bidderRequestWithOrtb2Device.ortb2.device.osv); + }); }); describe('no referer provided', () => { @@ -524,8 +553,58 @@ describe('sharethrough adapter spec', function () { ]); }); + it('should correctly harvest battr values for banner if present in mediaTypes.banner of impression and battr is not defined in ortb2Imp.banner', () => { + // assemble + const EXPECTED_BATTR_VALUES = [6, 7]; + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + + it('should not include battr values for banner if NOT present in mediaTypes.banner of impression and battr is not defined in ortb2Imp.banner', () => { + // assemble + delete bidRequests[0].mediaTypes.banner.battr; + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + + // assert + expect(builtRequest.data.imp[0].banner.battr).to.be.undefined; + }); + + it('should prefer battr values from mediaTypes.banner over ortb2Imp.banner', () => { + // assemble + deepSetValue(bidRequests[0], 'ortb2Imp.banner.battr', [1, 2, 3]); + const EXPECTED_BATTR_VALUES = [6, 7]; // values from mediaTypes.banner + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + + it('should use battr values from ortb2Imp.banner if mediaTypes.banner.battr is not present', () => { + // assemble + delete bidRequests[0].mediaTypes.banner.battr; + const EXPECTED_BATTR_VALUES = [1, 2, 3]; + deepSetValue(bidRequests[0], 'ortb2Imp.banner.battr', EXPECTED_BATTR_VALUES); + + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; + const ACTUAL_BATTR_VALUES = builtRequest.data.imp[0].banner.battr + + // assert + expect(ACTUAL_BATTR_VALUES).to.deep.equal(EXPECTED_BATTR_VALUES); + }); + it('should default to pos 0 if not provided', () => { - delete bidRequests[0].mediaTypes; + delete bidRequests[0].mediaTypes.banner.pos; const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[0]; const bannerImp = builtRequest.data.imp[0].banner; @@ -541,7 +620,7 @@ describe('sharethrough adapter spec', function () { 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.linearity).to.equal(1); expect(videoImp.minduration).to.equal(10); expect(videoImp.maxduration).to.equal(30); expect(videoImp.playbackmethod).to.deep.equal([1]); @@ -555,91 +634,71 @@ describe('sharethrough adapter spec', function () { expect(videoImp.skipafter).to.equal(20); expect(videoImp.placement).to.equal(1); expect(videoImp.delivery).to.equal(1); + expect(videoImp.battr).to.deep.equal([13, 14]); expect(videoImp.companiontype).to.equal('companion type'); expect(videoImp.companionad).to.equal('companion ad'); }); - it('should set defaults if no value provided', () => { + it('should set defaults in some circumstances if no value provided', () => { delete bidRequests[1].mediaTypes.video.pos; - delete bidRequests[1].mediaTypes.video.skip; - delete bidRequests[1].mediaTypes.video.linearity; - delete bidRequests[1].mediaTypes.video.minduration; - delete bidRequests[1].mediaTypes.video.maxduration; - delete bidRequests[1].mediaTypes.video.playbackmethod; - delete bidRequests[1].mediaTypes.video.api; - delete bidRequests[1].mediaTypes.video.mimes; - delete bidRequests[1].mediaTypes.video.protocols; delete bidRequests[1].mediaTypes.video.playerSize; - delete bidRequests[1].mediaTypes.video.startdelay; - delete bidRequests[1].mediaTypes.video.skipmin; - delete bidRequests[1].mediaTypes.video.skipafter; - delete bidRequests[1].mediaTypes.video.placement; - delete bidRequests[1].mediaTypes.video.delivery; - delete bidRequests[1].mediaTypes.video.companiontype; - delete bidRequests[1].mediaTypes.video.companionad; const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; const videoImp = builtRequest.data.imp[0].video; expect(videoImp.pos).to.equal(0); - expect(videoImp.skip).to.equal(0); - expect(videoImp.linearity).to.equal(1); - expect(videoImp.minduration).to.equal(5); - expect(videoImp.maxduration).to.equal(60); - expect(videoImp.playbackmethod).to.deep.equal([2]); - expect(videoImp.api).to.deep.equal([2]); - expect(videoImp.mimes).to.deep.equal(['video/mp4']); - expect(videoImp.protocols).to.deep.equal([2, 3, 5, 6, 7, 8]); expect(videoImp.w).to.equal(640); expect(videoImp.h).to.equal(360); - expect(videoImp.startdelay).to.equal(0); - expect(videoImp.skipmin).to.equal(0); - expect(videoImp.skipafter).to.equal(0); - expect(videoImp.placement).to.equal(1); - expect(videoImp.delivery).to.be.undefined; - expect(videoImp.companiontype).to.be.undefined; - expect(videoImp.companionad).to.be.undefined; }); - describe('outstream', () => { - it('should use placement value if provided', () => { - bidRequests[1].mediaTypes.video.context = 'outstream'; - bidRequests[1].mediaTypes.video.placement = 3; - - const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; - const videoImp = builtRequest.data.imp[0].video; + it('should not set values in some circumstances when non-valid values are supplied', () => { + // arrange + bidRequests[1].mediaTypes.video.api = 1; // non-array value, will not be used + bidRequests[1].mediaTypes.video.battr = undefined; // non-array value, will not be used + bidRequests[1].mediaTypes.video.mimes = 'video/3gpp'; // non-array value, will not be used + bidRequests[1].mediaTypes.video.playbackmethod = null; // non-array value, will not be used + bidRequests[1].mediaTypes.video.protocols = []; // empty array, will not be used - expect(videoImp.placement).to.equal(3); - }); + // act + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; - it('should default placement to 4 if not provided', () => { - bidRequests[1].mediaTypes.video.context = 'outstream'; + // assert + expect(videoImp.api).to.be.undefined; + expect(videoImp.battr).to.be.undefined; + expect(videoImp.mimes).to.be.undefined; + expect(videoImp.playbackmethod).to.be.undefined; + expect(videoImp.protocols).to.be.undefined; + }); - const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; - const videoImp = builtRequest.data.imp[0].video; + it('should not set a property if no corresponding property is detected on mediaTypes.video', () => { + // arrange + const propertiesToConsider = [ + 'api', 'battr', 'companionad', 'companiontype', 'delivery', 'linearity', 'maxduration', 'mimes', 'minduration', 'placement', 'playbackmethod', 'plcmt', 'protocols', 'skip', 'skipafter', 'skipmin', 'startdelay' + ] - expect(videoImp.placement).to.equal(4); + // act + propertiesToConsider.forEach(propertyToConsider => { + delete bidRequests[1].mediaTypes.video[propertyToConsider]; }); + const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; + const videoImp = builtRequest.data.imp[0].video; - it('should not override "placement" value if "plcmt" prop is present', () => { - // ASSEMBLE - const ARBITRARY_PLACEMENT_VALUE = 99; - const ARBITRARY_PLCMT_VALUE = 100; - - bidRequests[1].mediaTypes.video.context = 'instream'; - bidRequests[1].mediaTypes.video.placement = ARBITRARY_PLACEMENT_VALUE; + // assert + propertiesToConsider.forEach(propertyToConsider => { + expect(videoImp[propertyToConsider]).to.be.undefined; + }); + }); - // adding "plcmt" property - this should prevent "placement" prop - // from getting overridden to 1 - bidRequests[1].mediaTypes.video['plcmt'] = ARBITRARY_PLCMT_VALUE; + describe('outstream', () => { + it('should use placement value if provided', () => { + bidRequests[1].mediaTypes.video.context = 'outstream'; + bidRequests[1].mediaTypes.video.placement = 3; - // ACT const builtRequest = spec.buildRequests(bidRequests, bidderRequest)[1]; const videoImp = builtRequest.data.imp[0].video; - // ASSERT - expect(videoImp.placement).to.equal(ARBITRARY_PLACEMENT_VALUE); - expect(videoImp.plcmt).to.equal(ARBITRARY_PLCMT_VALUE); + expect(videoImp.placement).to.equal(3); }); }); }); @@ -755,7 +814,7 @@ describe('sharethrough adapter spec', function () { const EXPECTED_AE_VALUE = 1; // ACT - bidderRequest['fledgeEnabled'] = true; + bidderRequest.paapi = {enabled: true}; const builtRequests = spec.buildRequests(bidRequests, bidderRequest); const ACTUAL_AE_VALUE = builtRequests[0].data.imp[0].ext.ae; diff --git a/test/spec/modules/shinezRtbBidAdapter_spec.js b/test/spec/modules/shinezRtbBidAdapter_spec.js index 3965cd69c5f..ebd2e987491 100644 --- a/test/spec/modules/shinezRtbBidAdapter_spec.js +++ b/test/spec/modules/shinezRtbBidAdapter_spec.js @@ -2,6 +2,9 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, + storage +} from 'modules/shinezRtbBidAdapter'; +import { hashCode, extractPID, extractCID, @@ -10,15 +13,14 @@ import { setStorageItem, tryParseJSON, getUniqueDealId, -} from 'modules/shinezRtbBidAdapter'; -import * as utils from 'src/utils.js'; +} from '../../../libraries/vidazooUtils/bidderUtils.js'; +import {parseUrl, deepClone} from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; -import {deepAccess} from 'src/utils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId', 'digitrustid']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'exchange'; @@ -107,9 +109,15 @@ const BIDDER_REQUEST = { 'ref': 'https://www.somereferrer.com' }, 'ortb2': { + 'site': { + 'content': { + 'language': 'en' + } + }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -181,7 +189,7 @@ const REQUEST = { function getTopWindowQueryParams() { try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); + const parsedUrl = parseUrl(window.top.document.URL, {decodeSearchAsString: true}); return parsedUrl.search; } catch (e) { return ''; @@ -328,7 +336,14 @@ describe('ShinezRtbBidAdapter', function () { startdelay: 0 } }, - gpid: '0123456789' + gpid: '0123456789', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -390,6 +405,13 @@ describe('ShinezRtbBidAdapter', function () { uqs: getTopWindowQueryParams(), 'ext.param1': 'loremipsum', 'ext.param2': 'dolorsitamet', + cat: [], + contentLang: 'en', + contentData: [], + isStorageAllowed: true, + pagecat: [], + userData: [], + coppa: 0 } }); }); @@ -463,7 +485,7 @@ describe('ShinezRtbBidAdapter', function () { }); it('should get meta from response metaData', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); + const serverResponse = deepClone(SERVER_RESPONSE); serverResponse.body.results[0].metaData = { advertiserDomains: ['sweetgum.io'], agencyName: 'Agency Name', @@ -496,7 +518,7 @@ describe('ShinezRtbBidAdapter', function () { }); it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); + const serverResponse = deepClone(SERVER_RESPONSE); delete serverResponse.body.results[0].exp; const responses = adapter.interpretResponse(serverResponse, REQUEST); expect(responses).to.have.length(1); @@ -507,16 +529,12 @@ describe('ShinezRtbBidAdapter', function () { describe('user id system', function () { TEST_ID_SYSTEMS.forEach((idSystemProvider) => { const id = Date.now().toString(); - const bid = utils.deepClone(BID); + const bid = deepClone(BID); const userId = (function () { switch (idSystemProvider) { - case 'digitrustid': - return {data: {id}}; case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -569,13 +587,13 @@ describe('ShinezRtbBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -583,7 +601,7 @@ describe('ShinezRtbBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -607,8 +625,8 @@ describe('ShinezRtbBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -619,7 +637,7 @@ describe('ShinezRtbBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/showheroes-bsBidAdapter_spec.js b/test/spec/modules/showheroes-bsBidAdapter_spec.js index 30e95b04ccf..b03f6eb9a8a 100644 --- a/test/spec/modules/showheroes-bsBidAdapter_spec.js +++ b/test/spec/modules/showheroes-bsBidAdapter_spec.js @@ -1,98 +1,67 @@ -import {expect} from 'chai' -import {spec} from 'modules/showheroes-bsBidAdapter.js' -import {newBidder} from 'src/adapters/bidderFactory.js' -import {VIDEO, BANNER} from 'src/mediaTypes.js' +import { expect } from 'chai' +import { spec } from 'modules/showheroes-bsBidAdapter.js' +import { syncAddFPDToBidderRequest } from '../../helpers/fpd.js'; +import 'modules/priceFloors.js'; +import 'modules/consentManagementTcf.js'; +import 'modules/consentManagementUsp.js'; +import 'modules/schain.js'; +import { VIDEO } from 'src/mediaTypes.js' const bidderRequest = { refererInfo: { - canonicalUrl: 'https://example.com' + page: 'https://example.com/home', + ref: 'https://referrer' } } const adomain = ['showheroes.com']; const gdpr = { - 'gdprConsent': { - 'apiVersion': 2, - 'consentString': 'BOEFEAyOEFEAyAHABDENAI4AAAB9vABAASA', - 'gdprApplies': true + gdprConsent: { + apiVersion: 2, + consentString: 'CONSENT', + vendorData: { purpose: { consents: { 1: true } } }, + gdprApplies: true, } } const uspConsent = '1---'; const schain = { - 'schain': { - 'validation': 'strict', - 'config': { - 'ver': '1.0', - 'complete': 1, - 'nodes': [ + schain: { + validation: 'strict', + config: { + ver: '1.0', + complete: 1, + nodes: [ { - 'asi': 'some.com', - 'sid': '00001', - 'hp': 1 + asi: 'some.com', + sid: '00001', + hp: 1 } ] } } } -const bidRequestCommonParams = { - 'bidder': 'showheroes-bs', - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[640, 480]], - 'bidId': '38b373e1e31c18', - 'bidderRequestId': '12e3ade2543ba6', - 'auctionId': '43aa080090a47f', -} - const bidRequestCommonParamsV2 = { - 'bidder': 'showheroes-bs', - 'params': { - 'unitId': 'AACBWAcof-611K4U', + bidder: 'showheroes-bs', + params: { + unitId: 'AACBWAcof-611K4U', }, - 'adUnitCode': 'adunit-code-1', - 'sizes': [[640, 480]], - 'bidId': '38b373e1e31c18', - 'bidderRequestId': '12e3ade2543ba6', - 'auctionId': '43aa080090a47f', -} - -const bidRequestVideo = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', - } - } - } -} - -const bidRequestOutstream = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'outstream', - } - } - } + adUnitCode: 'adunit-code-1', + bidId: '38b373e1e31c18', + bidderRequestId: '12e3ade2543ba6', + auctionId: '43aa080090a47f', } const bidRequestVideoV2 = { ...bidRequestCommonParamsV2, ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'instream', } } } @@ -101,541 +70,188 @@ const bidRequestVideoV2 = { const bidRequestOutstreamV2 = { ...bidRequestCommonParamsV2, ...{ - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'outstream' - } - } - } -} - -const bidRequestVideoVpaid = { - ...bidRequestCommonParams, - ...{ - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - 'vpaidMode': true, - }, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480], - 'context': 'instream', - } - } - } -} - -const bidRequestBanner = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 360]] - } - } - } -} - -const bidRequestBannerMultiSizes = { - ...bidRequestCommonParams, - ...{ - 'mediaTypes': { - 'banner': { - 'sizes': [[640, 360], [480, 320]] - } + mediaTypes: { + video: { + playerSize: [640, 480], + context: 'outstream' + }, } } } -const bidRequestVideoAndBanner = { - ...bidRequestCommonParams, - 'mediaTypes': { - ...bidRequestBanner.mediaTypes, - ...bidRequestVideo.mediaTypes - } -} - -describe('shBidAdapter', 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 requestV1 = { - 'params': { - 'playerId': '47427aa0-f11a-4d24-abca-1295a46a46cd', - } - } - expect(spec.isBidRequestValid(requestV1)).to.equal(true) - - const requestV2 = { - 'params': { - 'unitId': 'AACBTwsZVANd9NlB', - } - } - expect(spec.isBidRequestValid(requestV2)).to.equal(true) - }) - - it('should return false when required params are not passed', function () { - const request = { - 'params': {} - } - expect(spec.isBidRequestValid(request)).to.equal(false) - }) - }) - - describe('buildRequests', function () { - it('sends bid request to ENDPOINT via POST', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - expect(request.method).to.equal('POST') - - const requestV2 = spec.buildRequests([bidRequestVideoV2], bidderRequest) - expect(requestV2.method).to.equal('POST') - }) - - it('check sizes formats', function () { - const request = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'banner': { - 'sizes': [[320, 240]] - } - }, - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.size).to.have.property('width', 320); - expect(payload.size).to.have.property('height', 240); - - const request2 = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 360] - } - }, - }], bidderRequest) - const payload2 = request2.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload2.size).to.have.property('width', 640); - expect(payload2.size).to.have.property('height', 360); - }) - - it('should get size from mediaTypes when sizes property is empty', function () { - const request = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'video': { - 'playerSize': [640, 480] - } - }, - 'sizes': [], - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.size).to.have.property('width', 640); - expect(payload.size).to.have.property('height', 480); - - const request2 = spec.buildRequests([{ - 'params': {}, - 'mediaTypes': { - 'banner': { - 'sizes': [[320, 240]] - } - }, - 'sizes': [], - }], bidderRequest) - const payload2 = request2.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload2.size).to.have.property('width', 320); - expect(payload2.size).to.have.property('height', 240); - }) - - it('should attach valid params to the payload when type is video', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 2); - }) - - it('should attach valid params to the payload when type is video & vpaid mode on', function () { - const request = spec.buildRequests([bidRequestVideoVpaid], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 1); - }) - - it('should attach valid params to the payload when type is banner', function () { - const request = spec.buildRequests([bidRequestBanner], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', BANNER); - expect(payload).to.have.property('type', 5); - }) - - it('should attach valid params to the payload when type is banner (multi sizes)', function () { - const request = spec.buildRequests([bidRequestBannerMultiSizes], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', BANNER); - expect(payload).to.have.property('type', 5); - expect(payload).to.have.nested.property('size.width', 640); - expect(payload).to.have.nested.property('size.height', 360); - const payload2 = request.data.requests[1]; - expect(payload2).to.be.an('object'); - expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload2).to.have.property('mediaType', BANNER); - expect(payload2).to.have.property('type', 5); - expect(payload2).to.have.nested.property('size.width', 480); - expect(payload2).to.have.nested.property('size.height', 320); - }) - - it('should attach valid params to the payload when type is banner and video', function () { - const request = spec.buildRequests([bidRequestVideoAndBanner], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload).to.have.property('mediaType', VIDEO); - expect(payload).to.have.property('type', 2); - const payload2 = request.data.requests[1]; - expect(payload2).to.be.an('object'); - expect(payload2).to.have.property('playerId', '47427aa0-f11a-4d24-abca-1295a46a46cd'); - expect(payload2).to.have.property('mediaType', BANNER); - expect(payload2).to.have.property('type', 5); - }) - - it('should attach valid params to the payload when type is video (instream V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) - const payload = request.data.bidRequests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U'); - expect(payload.mediaTypes).to.eql({ - [VIDEO]: { - 'context': 'instream' - } - }); - }) - - it('should attach valid params to the payload when type is video (outstream V2)', function () { - const request = spec.buildRequests([bidRequestOutstreamV2], bidderRequest) - const payload = request.data.bidRequests[0]; - expect(payload).to.be.an('object'); - expect(payload).to.have.property('unitId', 'AACBWAcof-611K4U'); - expect(payload.mediaTypes).to.eql({ - [VIDEO]: { - 'context': 'outstream' - } - }); - }) - - it('passes gdpr & uspConsent if present', function () { - const request = spec.buildRequests([bidRequestVideo], { - ...bidderRequest, - ...gdpr, - uspConsent, - }) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.gdprConsent).to.eql(gdpr.gdprConsent) - expect(payload.uspConsent).to.eql(uspConsent) - }) - - it('passes gdpr & usp if present (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], { - ...bidderRequest, - ...gdpr, - uspConsent, - }) - const context = request.data.context; - expect(context).to.be.an('object'); - expect(context.gdprConsent).to.eql(gdpr.gdprConsent) - expect(context.uspConsent).to.eql(uspConsent) - }) - - it('passes schain object if present', function() { - const request = spec.buildRequests([{ - ...bidRequestVideo, - ...schain - }], bidderRequest) - const payload = request.data.requests[0]; - expect(payload).to.be.an('object'); - expect(payload.schain).to.eql(schain.schain); - }) - - it('passes schain object if present (V2)', function() { - const request = spec.buildRequests([{ - ...bidRequestVideoV2, - ...schain - }], bidderRequest) - const context = request.data.context; - expect(context).to.be.an('object'); - expect(context.schain).to.eql(schain.schain); - }) - }) - - describe('interpretResponse', function () { - it('handles nobid responses', function () { - expect(spec.interpretResponse({body: {}}, {data: {meta: {}}}).length).to.equal(0) - expect(spec.interpretResponse({body: []}, {data: {meta: {}}}).length).to.equal(0) - }) - - const vastTag = 'https://test.com/commercial/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6' - const vastXml = '' - - const basicResponse = { - 'cpm': 5, - 'currency': 'EUR', - 'mediaType': VIDEO, - 'context': 'instream', - 'bidId': '38b373e1e31c18', - 'size': {'width': 640, 'height': 480}, - 'vastTag': 'https:\/\/test.com\/commercial\/wrapper?player_id=47427aa0-f11a-4d24-abca-1295a46a46cd&ad_bidder=showheroes-bs&master_shadt=1&description_url=https%3A%2F%2Fbid-service.stage.showheroes.com%2Fvast%2Fad%2Fcache%2F4840b920-40e1-4e09-9231-60bbf088c8d6', - 'vastXml': vastXml, - 'adomain': adomain, - }; - - const responseVideo = { - 'bids': [{ - ...basicResponse, - }], +describe('shBidAdapter', () => { + it('validates request', () => { + const bid = { + params: { + testKey: 'testValue', + }, }; - - const responseVideoOutstream = { - 'bids': [{ - ...basicResponse, - 'context': 'outstream', - }], + expect(spec.isBidRequestValid(bid)).to.eql(false); + bid.params = { + unitId: 'test_unit', }; + expect(spec.isBidRequestValid(bid)).to.eql(true); + }); - const responseBanner = { - 'bids': [{ - ...basicResponse, - 'mediaType': BANNER, - }], + it('passes gdpr, usp, schain, floor in ortb request', () => { + const bidRequest = Object.assign({}, bidRequestVideoV2) + const fullRequest = { + bids: [bidRequestVideoV2], + ...bidderRequest, + ...gdpr, + ...schain, + ...{ uspConsent: uspConsent }, }; + bidRequest.schain = schain.schain.config; + const getFloorResponse = { currency: 'EUR', floor: 3 }; + bidRequest.getFloor = () => getFloorResponse; + const request = spec.buildRequests([bidRequest], syncAddFPDToBidderRequest(fullRequest)); + const payload = request.data; + expect(payload.regs.ext.gdpr).to.eql(Number(gdpr.gdprConsent.gdprApplies)); + expect(payload.regs.ext.us_privacy).to.eql(uspConsent); + expect(payload.user.ext.consent).to.eql(gdpr.gdprConsent.consentString); + expect(payload.source.ext.schain).to.eql(bidRequest.schain); + expect(payload.test).to.eql(0); + expect(payload.imp[0].bidfloor).eql(3); + expect(payload.imp[0].bidfloorcur).eql('EUR'); + expect(payload.imp[0].displaymanager).eql('Prebid.js'); + expect(payload.site.page).to.eql('https://example.com/home'); + }); - const basicResponseV2 = { - 'requestId': '38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'cpm': 1, - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'advertiserDomain': [], - 'callbacks': { - 'won': ['https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok'] - }, - 'mediaType': 'video', - 'adomain': adomain, - }; + describe('interpretResponse', function () { + const vastXml = '' + const callback_won = 'https://test.com/track/?ver=15&session_id=01ecd03ce381505ccdeb88e555b05001&category=request_session&type=event&request_session_id=01ecd03ce381505ccdeb88e555b05001&label=prebid_won&reason=ok' + const basicResponse = { + cur: 'EUR', + seatbid: [{ + bid: [{ + price: 1, + w: 640, + h: 480, + adm: vastXml, + impid: '38b373e1e31c18', + crid: 'c_38b373e1e31c18', + adomain: adomain, + ext: { + callbacks: { + won: [callback_won], + }, + extra: 'test', + }, + }], + seat: 'showheroes', + }] + } const vastUrl = 'https://test.com/vast/?zid=AACBWAcof-611K4U&u=https://example.org/?foo=bar&gdpr=0&cs=XXXXXXXXXXXXXXXXXXXX&sid=01ecd03ce381505ccdeb88e555b05001&width=300&height=200&prebidmode=1' - const responseVideoV2 = { - 'bidResponses': [{ - ...basicResponseV2, - 'context': 'instream', - 'vastUrl': vastUrl, - }], - }; - - const responseVideoOutstreamV2 = { - 'bidResponses': [{ - ...basicResponseV2, - 'context': 'outstream', - 'ad': '', - }], - }; - - it('should get correct bid response when type is video', function () { - const request = spec.buildRequests([bidRequestVideo], bidderRequest) - const expectedResponse = [ - { - 'cpm': 5, - 'creativeId': 'c_38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'netRevenue': true, - 'vastUrl': vastTag, - 'vastXml': vastXml, - 'requestId': '38b373e1e31c18', - 'ttl': 300, - 'adResponse': { - 'content': vastXml - }, - 'meta': { - 'advertiserDomains': adomain + if (FEATURES.VIDEO) { + it('should get correct bid response when type is video (V2)', function () { + const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) + const expectedResponse = [ + { + cpm: 1, + creativeId: 'c_38b373e1e31c18', + creative_id: 'c_38b373e1e31c18', + currency: 'EUR', + width: 640, + height: 480, + playerHeight: 480, + playerWidth: 640, + mediaType: 'video', + netRevenue: true, + requestId: '38b373e1e31c18', + ttl: 300, + meta: { + advertiserDomains: adomain + }, + vastXml: vastXml, + callbacks: { + won: [callback_won], + }, + extra: 'test', } - } - ] + ] - const result = spec.interpretResponse({'body': responseVideo}, request) - expect(result).to.deep.equal(expectedResponse) - }) + const result = spec.interpretResponse({ 'body': basicResponse }, request) + expect(result).to.deep.equal(expectedResponse) + }) - it('should get correct bid response when type is video (V2)', function () { - const request = spec.buildRequests([bidRequestVideoV2], bidderRequest) - const expectedResponse = [ - { - 'cpm': 1, - 'creativeId': 'c_38b373e1e31c18', - 'adUnitCode': 'adunit-code-1', - 'currency': 'EUR', - 'width': 640, - 'height': 480, - 'mediaType': 'video', - 'netRevenue': true, - 'vastUrl': vastUrl, - 'requestId': '38b373e1e31c18', - 'ttl': 300, - 'meta': { - 'advertiserDomains': adomain + it('should get correct bid response when type is outstream (slot V2)', function () { + window.myRenderer = { + renderAd: function() { + return null; } } - ] - - const result = spec.interpretResponse({'body': responseVideoV2}, request) - expect(result).to.deep.equal(expectedResponse) - }) - - it('should get correct bid response when type is banner', function () { - const request = spec.buildRequests([bidRequestBanner], bidderRequest) - - const result = spec.interpretResponse({'body': responseBanner}, request) - expect(result[0]).to.have.property('mediaType', BANNER); - expect(result[0].ad).to.include('", + 'cpm': 0.1, + 'ttl': 120, + 'creativeId': '123', + 'netRevenue': true, + 'currency': 'USD', + 'dealId': 'HASH', + 'sid': 1234, + 'meta': { + 'advertiserDomains': [ + 'smartyads.com' + ], + 'dchain': { + 'ver': '1.0', + 'complete': 0, + 'nodes': [ + { + 'name': 'smartyads' + } + ] + } + }, + 'metrics': { + 'requestBids.usp': 0.20000000298023224, + 'requestBids.gdpr': 67.19999999925494, + 'requestBids.fpd': 4.299999997019768, + 'requestBids.validate': 0.29999999701976776, + 'requestBids.makeRequests': 1.800000000745058, + 'requestBids.total': 740.9000000022352, + 'requestBids.callBids': 663, + 'adapter.client.validate': 0, + 'adapters.client.smartyads.validate': 0, + 'adapter.client.buildRequests': 1, + 'adapters.client.smartyads.buildRequests': 1, + 'adapter.client.total': 661.6999999992549, + 'adapters.client.smartyads.total': 661.6999999992549, + 'adapter.client.net': 657.8999999985099, + 'adapters.client.smartyads.net': 657.8999999985099, + 'adapter.client.interpretResponse': 0, + 'adapters.client.smartyads.interpretResponse': 0, + 'addBidResponse.validate': 0.19999999925494194, + 'addBidResponse.categoryTranslation': 0, + 'addBidResponse.dchain': 0.10000000149011612, + 'addBidResponse.dsa': 0, + 'addBidResponse.multibid': 0, + 'addBidResponse.total': 1.5999999977648258, + 'render.pending': 368.80000000074506, + 'render.e2e': 1109.7000000029802 + }, + 'adapterCode': 'smartyads', + 'originalCpm': 0.1, + 'originalCurrency': 'USD', + 'responseTimestamp': 1715350155081, + 'requestTimestamp': 1715350154420, + 'bidder': 'smartyads', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'timeToRespond': 661, + 'pbLg': '0.00', + 'pbMg': '0.10', + 'pbHg': '0.10', + 'pbAg': '0.10', + 'pbDg': '0.10', + 'pbCg': '', + 'size': '300x250', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '3c81d46b03abb', + 'hb_pb': '0.10', + 'hb_size': '300x250', + 'hb_deal': 'HASH', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'smartyads.com', + 'hb_crid': '123' + }, + 'latestTargetedAuctionId': '5c0d10bf-96cb-4afa-92ac-2ef75470da22', + 'status': 'rendered', + 'params': [ + { + 'sourceid': '983', + 'host': 'prebid', + 'accountid': '18349', + 'traffic': 'banner' + } + ] + } + }; + + after(function () { + smartyadsAnalytics.disableAnalytics(); + }); + + describe('main test', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + sinon.spy(smartyadsAnalytics, 'track'); + }); + + afterEach(function () { + events.getEvents.restore(); + smartyadsAnalytics.disableAnalytics(); + smartyadsAnalytics.track.restore(); + }); + + it('test auctionEnd', function () { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.AUCTION_END, auctionEnd); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('auctionData'); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.AUCTION_END); + expect(message.auctionData).to.have.property('auctionId'); + expect(message.auctionData.bidderRequests).to.have.length(1); + }); + + it('test bidWon', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.BID_WON); + expect(message).to.have.property('bid'); + expect(message.bid).to.have.property('bidder').and.to.equal('smartyads'); + expect(message.bid).to.have.property('cpm').and.to.equal(bidWon.cpm); + }); + + it('test adRender', function() { + adapterManager.registerAnalyticsAdapter({ + code: 'smartyads', + adapter: smartyadsAnalytics + }); + + adapterManager.enableAnalytics({ + provider: 'smartyads', + }); + + events.emit(EVENTS.AD_RENDER_SUCCEEDED, renderData); + expect(server.requests.length).to.equal(1); + let message = JSON.parse(server.requests[0].requestBody); + expect(message).to.have.property('eventType').and.to.equal(EVENTS.AD_RENDER_SUCCEEDED); + expect(message).to.have.property('renderData'); + expect(message.renderData).to.have.property('doc'); + expect(message.renderData).to.have.property('doc'); + expect(message.renderData).to.have.property('bid'); + expect(message.renderData.bid).to.have.property('adId').and.to.equal(renderData.bid.adId); + }); + }); +}); diff --git a/test/spec/modules/smartyadsBidAdapter_spec.js b/test/spec/modules/smartyadsBidAdapter_spec.js index 1b592e142c3..65480ee11e6 100644 --- a/test/spec/modules/smartyadsBidAdapter_spec.js +++ b/test/spec/modules/smartyadsBidAdapter_spec.js @@ -61,12 +61,10 @@ describe('SmartyadsAdapter', function () { 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', 'coppa', 'eeid', 'ifa'); + expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', 'host', 'page', 'placements', 'coppa', 'eeid', 'ifa'); expect(data.deviceWidth).to.be.a('number'); expect(data.deviceHeight).to.be.a('number'); expect(data.coppa).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]; @@ -126,8 +124,8 @@ describe('SmartyadsAdapter', function () { expect(dataItem.width).to.equal(300); expect(dataItem.height).to.equal(250); expect(dataItem.ad).to.equal('Test'); - expect(dataItem.meta).to.have.property('advertiserDomains') - expect(dataItem.meta.advertiserDomains).to.deep.equal(['example.com']); + expect(dataItem.meta).to.have.property('advertiserDomains'); + expect(dataItem.meta.advertiserDomains).to.be.an('array'); expect(dataItem.ttl).to.equal(120); expect(dataItem.creativeId).to.equal('2'); expect(dataItem.netRevenue).to.be.true; @@ -152,7 +150,7 @@ describe('SmartyadsAdapter', 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'); @@ -183,7 +181,7 @@ describe('SmartyadsAdapter', 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); @@ -263,7 +261,7 @@ describe('SmartyadsAdapter', function () { }); }); describe('getUserSyncs', function () { - const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a&gdpr=0&gdpr_consent=&type=iframe&us_privacy=&gpp='; + const syncUrl = 'https://as.ck-ie.com/prebidjs?p=7c47322e527cf8bdeb7facc1bb03387a/iframe?pbjs=1&coppa=0'; const syncOptions = { iframeEnabled: true }; @@ -277,79 +275,4 @@ describe('SmartyadsAdapter', function () { ]); }); }); - - describe('onBidWon', function () { - it('should exists', function () { - expect(spec.onBidWon).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bid won notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'winTest': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onBidWon(bid); - expect(server.requests.length).to.equal(1); - }); - }); - - describe('onTimeout', function () { - it('should exists', function () { - expect(spec.onTimeout).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bid timeout notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'bidTimeout': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onTimeout(bid); - expect(server.requests.length).to.equal(1); - }); - }); - - describe('onBidderError', function () { - it('should exists', function () { - expect(spec.onBidderError).to.exist.and.to.be.a('function'); - }); - - it('should send a valid bidder error notice', function () { - const bid = { - 'c': 'o', - 'm': 'prebid', - 'secret_key': 'prebid_js', - 'bidderError': '1', - 'postData': [{ - 'bidder': 'smartyads', - 'params': [ - {'host': 'prebid', - 'accountid': '123', - 'sourceid': '12345' - }] - }] - }; - spec.onBidderError(bid); - expect(server.requests.length).to.equal(1); - }); - }); }); diff --git a/test/spec/modules/smartytechBidAdapter_spec.js b/test/spec/modules/smartytechBidAdapter_spec.js index 6b3147859bf..3b6d5d0c5fc 100644 --- a/test/spec/modules/smartytechBidAdapter_spec.js +++ b/test/spec/modules/smartytechBidAdapter_spec.js @@ -204,7 +204,11 @@ function mockResponseData(requestData) { creativeId: `creative-id-${index}`, cpm: Math.floor(Math.random() * 100), currency: `UAH-${rndIndex}`, - mediaType: mediaType + mediaType: mediaType, + meta: { + primaryCatId: 'IAB2-2', + secondaryCatIds: ['IAB2-14', 'IAB2-6'] + } }; }); return { diff --git a/test/spec/modules/smilewantedBidAdapter_spec.js b/test/spec/modules/smilewantedBidAdapter_spec.js index 99c4034610f..1c71c7bee07 100644 --- a/test/spec/modules/smilewantedBidAdapter_spec.js +++ b/test/spec/modules/smilewantedBidAdapter_spec.js @@ -76,6 +76,49 @@ const DISPLAY_REQUEST_WITH_POSITION_TYPE = [{ }, }]; +const SCHAIN = { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + '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' + } + ] +}; + +const DISPLAY_REQUEST_WITH_SCHAIN = [{ + adUnitCode: 'sw_300x250', + bidId: '12345', + sizes: [ + [300, 250], + [300, 200] + ], + bidder: 'smilewanted', + params: { + zoneId: 1, + }, + requestId: 'request_abcd1234', + ortb2Imp: { + ext: { + tid: 'trans_abcd1234', + } + }, + schain: SCHAIN, +}]; + const BID_RESPONSE_DISPLAY = { body: { cpm: 3, @@ -580,8 +623,21 @@ describe('smilewantedBidAdapterTests', function () { expect(requestContent).to.have.property('positionType').and.to.equal('infeed'); }); + it('SmileWanted - Verify if schain is well passed', function () { + const request = spec.buildRequests(DISPLAY_REQUEST_WITH_SCHAIN, {}); + const requestContent = JSON.parse(request[0].data); + expect(requestContent).to.have.property('schain').and.to.equal('1.0,1!exchange1.com,1234,1,bid-request-1,publisher,publisher.com,!exchange2.com,abcd,1,bid-request-2,intermediary,intermediary.com,'); + }); + + it('SmileWanted - Verify user sync - empty data', function () { + let syncs = spec.getUserSyncs({iframeEnabled: true}, {}, {}, null); + expect(syncs).to.have.lengthOf(1); + expect(syncs[0].type).to.equal('iframe'); + expect(syncs[0].url).to.equal('https://csync.smilewanted.com'); + }); + it('SmileWanted - Verify user sync', function () { - var syncs = spec.getUserSyncs({iframeEnabled: true}, {}, { + let syncs = spec.getUserSyncs({iframeEnabled: true}, {}, { consentString: 'foo' }, '1NYN'); expect(syncs).to.have.lengthOf(1); diff --git a/test/spec/modules/smootBidAdapter_spec.js b/test/spec/modules/smootBidAdapter_spec.js new file mode 100644 index 00000000000..cf72b41b348 --- /dev/null +++ b/test/spec/modules/smootBidAdapter_spec.js @@ -0,0 +1,595 @@ +import { expect } from 'chai'; +import { spec } from '../../../modules/smootBidAdapter.js'; +import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; +import { getUniqueIdentifierStr } from '../../../src/utils.js'; + +const bidder = 'smoot'; + +describe('SmootBidAdapter', function () { + const userIdAsEids = [ + { + source: 'test.org', + uids: [ + { + id: '01**********', + atype: 1, + ext: { + third: '01***********', + }, + }, + ], + }, + ]; + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: { + placementId: 'testBanner', + }, + userIdAsEids, + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [VIDEO]: { + playerSize: [[300, 300]], + minduration: 5, + maxduration: 60, + }, + }, + params: { + placementId: 'testVideo', + }, + userIdAsEids, + }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [NATIVE]: { + native: { + title: { + required: true, + }, + body: { + required: true, + }, + icon: { + required: true, + size: [64, 64], + }, + }, + }, + }, + params: { + placementId: 'testNative', + }, + userIdAsEids, + }, + ]; + + const invalidBid = { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: {}, + }; + + const bidderRequest = { + uspConsent: '1---', + gdprConsent: { + consentString: + 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {}, + }, + refererInfo: { + referer: 'https://test.com', + }, + timeout: 500, + }; + + 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 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('object'); + 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); + expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]], + }, + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids, + }, + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf([ + 'testBanner', + 'testVideo', + 'testNative', + ]); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal( + bidderRequest.gdprConsent.consentString + ); + 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; + }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8], + }; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }); + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }); + }); + + 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://usync.smxconv.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://usync.smxconv.com/image?pbjs=1&ccpa_consent=1---&coppa=0' + ); + }); + it('Should return array of objects with proper sync config , include GPP', function () { + const syncData = spec.getUserSyncs( + {}, + {}, + {}, + {}, + { + gppString: 'abc123', + applicableSections: [8], + } + ); + 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://usync.smxconv.com/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0' + ); + }); + }); +}); diff --git a/test/spec/modules/snigelBidAdapter_spec.js b/test/spec/modules/snigelBidAdapter_spec.js index 828aec9491c..69ab85ba825 100644 --- a/test/spec/modules/snigelBidAdapter_spec.js +++ b/test/spec/modules/snigelBidAdapter_spec.js @@ -181,6 +181,50 @@ describe('snigelBidAdapter', function () { expect(data.placements[2].refresh.count).to.equal(1); expect(data.placements[2].refresh.time).to.be.greaterThanOrEqual(0); }); + + it('should increment auction counter upon every request', function () { + const bidderRequest = makeBidderRequest({}); + + let request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + let data = JSON.parse(request.data); + const previousCounter = data.counter; + + request = spec.buildRequests([], bidderRequest); + expect(request).to.have.property('data'); + data = JSON.parse(request.data); + expect(data.counter).to.equal(previousCounter + 1); + }); + + it('should increment placement counter for each placement', function () { + const bidderRequest = Object.assign({}, BASE_BIDDER_REQUEST); + const topLeaderboard = makeBidRequest({adUnitCode: 'top_leaderboard', params: {placement: 'ros'}}); + const bottomLeaderboard = makeBidRequest({adUnitCode: 'bottom_leaderboard', params: {placement: 'ros'}}); + const sidebar = makeBidRequest({adUnitCode: 'sidebar', params: {placement: 'other'}}); + + let request = spec.buildRequests([topLeaderboard, bottomLeaderboard, sidebar], bidderRequest); + expect(request).to.have.property('data'); + let data = JSON.parse(request.data); + const previousCounters = {}; + data.placements.forEach((placement) => { + previousCounters[placement.name] = Math.max(previousCounters[placement.name] || 0, placement.counter); + }); + + request = spec.buildRequests([topLeaderboard, bottomLeaderboard, sidebar], bidderRequest); + expect(request).to.have.property('data'); + data = JSON.parse(request.data); + expect(data).to.have.property('placements'); + expect(data.placements.length).to.equal(3); + expect(data.placements[0].id).to.equal('top_leaderboard'); + expect(previousCounters).to.have.property(data.placements[0].name); + expect(data.placements[0].counter).to.equal(previousCounters[data.placements[0].name] + 1); + expect(data.placements[1].id).to.equal('bottom_leaderboard'); + expect(previousCounters).to.have.property(data.placements[1].name); + expect(data.placements[1].counter).to.equal(previousCounters[data.placements[1].name] + 2); + expect(data.placements[2].id).to.equal('sidebar'); + expect(previousCounters).to.have.property(data.placements[2].name); + expect(data.placements[2].counter).to.equal(previousCounters[data.placements[2].name] + 1); + }); }); describe('interpretResponse', function () { @@ -347,7 +391,7 @@ describe('snigelBidAdapter', function () { expect(sync.url).to.equal(`https://somesyncurl?gdpr=1&gdpr_consent=${DUMMY_GDPR_CONSENT_STRING}`); }); - it('should omit session ID if no device access', function() { + it('should omit session ID if no device access', function () { const bidderRequest = makeBidderRequest(); const unregisterRule = registerActivityControl(ACTIVITY_ACCESS_DEVICE, 'denyAccess', () => { return {allow: false, reason: 'no consent'}; @@ -373,9 +417,9 @@ describe('snigelBidAdapter', function () { }, vendor: { consents: {[spec.gvlid]: true}, - } + }, }, - } + }, }); let request = spec.buildRequests([], baseBidderRequest); expect(request).to.have.property('data'); @@ -388,25 +432,14 @@ describe('snigelBidAdapter', function () { data = JSON.parse(request.data); expect(data.gdprConsent).to.be.false; - bidderRequest = {...baseBidderRequest, ...{gdprConsent: {vendorData: {vendor: {consents: {[spec.gvlid]: false}}}}}}; + bidderRequest = { + ...baseBidderRequest, + ...{gdprConsent: {vendorData: {vendor: {consents: {[spec.gvlid]: false}}}}}, + }; request = spec.buildRequests([], bidderRequest); expect(request).to.have.property('data'); data = JSON.parse(request.data); expect(data.gdprConsent).to.be.false; }); - - it('should increment auction counter upon every request', function() { - const bidderRequest = makeBidderRequest({}); - - let request = spec.buildRequests([], bidderRequest); - expect(request).to.have.property('data'); - let data = JSON.parse(request.data); - const previousCounter = data.counter; - - request = spec.buildRequests([], bidderRequest); - expect(request).to.have.property('data'); - data = JSON.parse(request.data); - expect(data.counter).to.equal(previousCounter + 1); - }); }); }); diff --git a/test/spec/modules/sonobiAnalyticsAdapter_spec.js b/test/spec/modules/sonobiAnalyticsAdapter_spec.js deleted file mode 100644 index c34de91dd9f..00000000000 --- a/test/spec/modules/sonobiAnalyticsAdapter_spec.js +++ /dev/null @@ -1,85 +0,0 @@ -import sonobiAnalytics, {DEFAULT_EVENT_URL} from 'modules/sonobiAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {server} from 'test/mocks/xhr.js'; -import { EVENTS } from 'src/constants.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('Sonobi Prebid Analytic', function () { - var clock; - - describe('enableAnalytics', function () { - beforeEach(function () { - sinon.stub(events, 'getEvents').returns([]); - clock = sinon.useFakeTimers(Date.now()); - }); - - afterEach(function () { - events.getEvents.restore(); - clock.restore(); - }); - - after(function () { - sonobiAnalytics.disableAnalytics(); - }); - - it('should catch all events', function (done) { - const initOptions = { - pubId: 'A3B254F', - siteId: '1234', - delay: 100 - }; - - sonobiAnalytics.enableAnalytics(initOptions) - - const bid = { - bidderCode: 'sonobi_test_bid', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '1234', - auctionId: '13', - responseTimestamp: 1496410856397, - requestTimestamp: 1496410856295, - cpm: 1.13, - bidder: 'sonobi', - adUnitCode: 'dom-sample-id', - timeToRespond: 100, - placementCode: 'placementtest' - }; - - // Step 1: Initialize adapter - adapterManager.enableAnalytics({ - provider: 'sonobi', - options: initOptions - }); - - // Step 2: Send init auction event - events.emit(EVENTS.AUCTION_INIT, { config: initOptions, auctionId: '13', timestamp: Date.now() }); - - expect(sonobiAnalytics.initOptions).to.have.property('pubId', 'A3B254F'); - expect(sonobiAnalytics.initOptions).to.have.property('siteId', '1234'); - expect(sonobiAnalytics.initOptions).to.have.property('delay', 100); - // Step 3: Send bid requested event - events.emit(EVENTS.BID_REQUESTED, { bids: [bid], auctionId: '13' }); - - // Step 4: Send bid response event - events.emit(EVENTS.BID_RESPONSE, bid); - - // Step 5: Send bid won event - events.emit(EVENTS.BID_WON, bid); - - // Step 6: Send bid timeout event - events.emit(EVENTS.BID_TIMEOUT, { auctionId: '13' }); - - // Step 7: Send auction end event - events.emit(EVENTS.AUCTION_END, { auctionId: '13', bidsReceived: [bid] }); - - clock.tick(5000); - const req = server.requests.find(req => req.url.indexOf(DEFAULT_EVENT_URL) !== -1); - expect(JSON.parse(req.requestBody)).to.have.length(3) - done(); - }); - }); -}); diff --git a/test/spec/modules/sovrnAnalyticsAdapter_spec.js b/test/spec/modules/sovrnAnalyticsAdapter_spec.js deleted file mode 100644 index 7945bdc9910..00000000000 --- a/test/spec/modules/sovrnAnalyticsAdapter_spec.js +++ /dev/null @@ -1,530 +0,0 @@ -import sovrnAnalyticsAdapter from '../../../modules/sovrnAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import adaptermanager from 'src/adapterManager.js'; -import {server} from 'test/mocks/xhr.js'; -import {expectEvents, fireEvents} from '../../helpers/analytics.js'; -import { EVENTS } from 'src/constants.js'; - -var assert = require('assert'); - -let events = require('src/events'); - -/** - * Emit analytics events - * @param {Array} eventType - array of objects to define the events that will fire - * @param {object} event - key is eventType, value is event - * @param {string} auctionId - the auction id to attached to the events - */ -function emitEvent(eventType, event, auctionId) { - event.auctionId = auctionId; - events.emit(EVENTS[eventType], event); -} - -let auctionStartTimestamp = Date.now(); -let timeout = 3000; -let auctionInit = { - timestamp: auctionStartTimestamp, - timeout: timeout -}; -let bidderCode = 'sovrn'; -let bidderRequestId = '123bri'; -let adUnitCode = 'div'; -let adUnitCode2 = 'div2'; -let bidId = 'bidid'; -let bidId2 = 'bidid2'; -let tId = '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a'; -let tId2 = '99dca3ee-a80a-46d7-a4a0-cbcba463d97e'; -let bidRequested = { - auctionStart: auctionStartTimestamp, - bidderCode: bidderCode, - bidderRequestId: bidderRequestId, - bids: [ - { - adUnitCode: adUnitCode, - bidId: bidId, - bidder: bidderCode, - bidderRequestId: '10340af0c7dc72', - sizes: [[300, 250]], - startTime: auctionStartTimestamp + 100, - transactionId: tId - }, - { - adUnitCode: adUnitCode2, - bidId: bidId2, - bidder: bidderCode, - bidderRequestId: '10340af0c7dc72', - sizes: [[300, 250]], - startTime: auctionStartTimestamp + 100, - transactionId: tId2 - } - ], - doneCbCallCount: 1, - start: auctionStartTimestamp, - timeout: timeout -}; -let bidResponse = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '3870e27a5752fb', - mediaType: 'banner', - source: 'client', - requestId: bidId, - cpm: 0.8584999918937682, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: bidderCode, - adUnitCode: adUnitCode, - timeToRespond: 50, - pbLg: '0.50', - pbMg: '0.80', - pbHg: '0.85', - pbAg: '0.85', - pbDg: '0.85', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: bidderCode, - hb_adid: '3870e27a5752fb', - hb_pb: '0.85' - }, - status: 'rendered' -}; - -let bidResponse2 = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '9999e27a5752fb', - mediaType: 'banner', - source: 'client', - requestId: bidId2, - cpm: 0.12, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: bidderCode, - adUnitCode: adUnitCode2, - timeToRespond: 50, - pbLg: '0.10', - pbMg: '0.10', - pbHg: '0.10', - pbAg: '0.10', - pbDg: '0.10', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: bidderCode, - hb_adid: '9999e27a5752fb', - hb_pb: '0.10' - }, - status: 'rendered' -}; -let bidAdjustment = {}; -for (var k in bidResponse) bidAdjustment[k] = bidResponse[k]; -bidAdjustment.cpm = 0.8; -let bidAdjustmentNoMatchingRequest = { - bidderCode: 'not-sovrn', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '1', - mediaType: 'banner', - source: 'client', - requestId: '1', - cpm: 0.10, - creativeId: '', - dealId: null, - currency: 'USD', - netRevenue: true, - ad: '
divvy mcdiv
', - ttl: 60000, - responseTimestamp: auctionStartTimestamp + 150, - requestTimestamp: auctionStartTimestamp + 100, - bidder: 'not-sovrn', - adUnitCode: '', - timeToRespond: 50, - pbLg: '0.00', - pbMg: '0.10', - pbHg: '0.10', - pbAg: '0.10', - pbDg: '0.10', - pbCg: '', - size: '300x250', - adserverTargeting: { - hb_bidder: 'not-sovrn', - hb_adid: '1', - hb_pb: '0.10' - }, -}; -let bidResponseNoMatchingRequest = bidAdjustmentNoMatchingRequest; - -describe('Sovrn Analytics Adapter', function () { - beforeEach(() => { - sinon.stub(events, 'getEvents').returns([]); - }); - afterEach(() => { - events.getEvents.restore(); - }); - - describe('enableAnalytics ', function () { - beforeEach(() => { - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - - it('should catch all events if affiliate id present', function () { - adaptermanager.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - expectEvents().to.beTrackedBy(sovrnAnalyticsAdapter.track); - }); - - it('should catch no events if no affiliate id', function () { - adaptermanager.enableAnalytics({ - provider: 'sovrn', - options: { - } - }); - fireEvents(); - sinon.assert.callCount(sovrnAnalyticsAdapter.track, 0); - }); - }); - - describe('sovrnAnalyticsAdapter ', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should have correct type', function () { - assert.equal(sovrnAnalyticsAdapter.getAdapterType(), 'endpoint') - }) - }); - - describe('auction data collector ', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should create auctiondata record from init ', function () { - let auctionId = '123.123.123.123'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - assert(currentAuction); - let expectedTimeOutData = { - buffer: config.getConfig('timeoutBuffer'), - bidder: config.getConfig('bidderTimeout'), - }; - expect(currentAuction.auction.timeouts).to.deep.equal(expectedTimeOutData); - assert.equal(currentAuction.auction.payload, 'auction'); - assert.equal(currentAuction.auction.priceGranularity, config.getConfig('priceGranularity')) - assert.equal(currentAuction.auction.auctionId, auctionId); - assert.equal(currentAuction.auction.sovrnId, 123); - }); - it('should create a bidrequest object ', function() { - let auctionId = '234.234.234.234'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - assert(currentAuction); - let requests = currentAuction.auction.requests; - assert(requests); - assert.equal(requests.length, 1); - assert.equal(requests[0].bidderCode, bidderCode); - assert.equal(requests[0].bidderRequestId, bidderRequestId); - assert.equal(requests[0].timeout, timeout); - let bids = requests[0].bids; - assert(bids); - assert.equal(bids.length, 2); - assert.equal(bids[0].bidId, bidId); - assert.equal(bids[0].bidder, bidderCode); - assert.equal(bids[0].transactionId, tId); - assert.equal(bids[0].sizes.length, 1); - assert.equal(bids[0].sizes[0][0], 300); - assert.equal(bids[0].sizes[0][1], 250); - expect(requests[0]).to.not.have.property('doneCbCallCount'); - expect(requests[0]).to.not.have.property('auctionId'); - }); - it('should add results to the bid with response ', function () { - let auctionId = '345.345.345.345'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponse, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let returnedBid = currentAuction.auction.requests[0].bids[0]; - assert.equal(returnedBid.bidId, bidId); - assert.equal(returnedBid.bidder, bidderCode); - assert.equal(returnedBid.transactionId, tId); - assert.equal(returnedBid.sizes.length, 1); - assert.equal(returnedBid.sizes[0][0], 300); - assert.equal(returnedBid.sizes[0][1], 250); - assert.equal(returnedBid.adserverTargeting.hb_adid, '3870e27a5752fb'); - assert.equal(returnedBid.adserverTargeting.hb_bidder, bidderCode); - assert.equal(returnedBid.adserverTargeting.hb_pb, '0.85'); - assert.equal(returnedBid.cpm, 0.8584999918937682); - }); - it('should add new unsynced bid if no request exists for response ', function () { - let auctionId = '456.456.456.456'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponseNoMatchingRequest, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let requests = currentAuction.auction.requests; - assert(requests); - assert.equal(requests.length, 1); - let bidRequest = requests[0].bids[0]; - expect(bidRequest).to.not.have.property('adserverTargeting'); - expect(bidRequest).to.not.have.property('cpm'); - expect(currentAuction.auction.unsynced[0]).to.deep.equal(bidResponseNoMatchingRequest); - }); - it('should adjust the bid ', function () { - let auctionId = '567.567.567.567'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_ADJUSTMENT', bidResponse, auctionId); - emitEvent('BID_RESPONSE', bidAdjustment, auctionId); - - let auctionData = sovrnAnalyticsAdapter.getAuctions(); - let currentAuction = auctionData[auctionId]; - let returnedBid = currentAuction.auction.requests[0].bids[0]; - assert.equal(returnedBid.cpm, 0.8); - assert.equal(returnedBid.originalValues.cpm, 0.8584999918937682); - }); - }); - describe('auction data send ', function() { - let expectedPostBody = { - sovrnId: 123, - auctionId: '678.678.678.678', - payload: 'auction', - priceGranularity: 'medium', - }; - let expectedRequests = { - bidderCode: 'sovrn', - bidderRequestId: '123bri', - timeout: 3000 - }; - let expectedBids = { - adUnitCode: 'div', - bidId: 'bidid', - bidder: 'sovrn', - bidderRequestId: '10340af0c7dc72', - transactionId: '7aafa3ee-a80a-46d7-a4a0-cbcba463d97a', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '3870e27a5752fb', - mediaType: 'banner', - source: 'client', - cpm: 0.8584999918937682, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ttl: 60000, - timeToRespond: 50, - size: '300x250', - status: 'rendered', - isAuctionWinner: true - }; - let SecondAdUnitExpectedBids = { - adUnitCode: 'div2', - bidId: 'bidid2', - bidder: 'sovrn', - bidderRequestId: '10340af0c7dc72', - transactionId: '99dca3ee-a80a-46d7-a4a0-cbcba463d97e', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '9999e27a5752fb', - mediaType: 'banner', - source: 'client', - cpm: 0.12, - creativeId: 'cridprebidrtb', - dealId: null, - currency: 'USD', - netRevenue: true, - ttl: 60000, - timeToRespond: 50, - size: '300x250', - status: 'rendered', - isAuctionWinner: true - }; - let expectedAdServerTargeting = { - hb_bidder: 'sovrn', - hb_adid: '3870e27a5752fb', - hb_pb: '0.85' - }; - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should send auction data ', function () { - let auctionId = '678.678.678.678'; - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_REQUESTED', bidRequested, auctionId); - emitEvent('BID_RESPONSE', bidResponse, auctionId); - emitEvent('BID_RESPONSE', bidResponse2, auctionId) - emitEvent('AUCTION_END', {}, auctionId); - let requestBody = JSON.parse(server.requests[0].requestBody); - let requestsFromRequestBody = requestBody.requests[0]; - let bidsFromRequests = requestsFromRequestBody.bids[0]; - expect(requestBody).to.deep.include(expectedPostBody); - expect(requestBody.timeouts).to.deep.equal({buffer: 400, bidder: 3000}); - expect(requestsFromRequestBody).to.deep.include(expectedRequests); - expect(bidsFromRequests).to.deep.include(expectedBids); - let bidsFromRequests2 = requestsFromRequestBody.bids[1]; - expect(bidsFromRequests2).to.deep.include(SecondAdUnitExpectedBids); - expect(bidsFromRequests.adserverTargeting).to.deep.include(expectedAdServerTargeting); - }); - }); - describe('bid won data send ', function() { - let auctionId = '789.789.789.789'; - let creativeId = 'cridprebidrtb'; - let requestId = 'requestId69'; - let bidWonEvent = { - ad: 'html', - adId: 'adId', - adUnitCode: adUnitCode, - auctionId: auctionId, - bidder: bidderCode, - bidderCode: bidderCode, - cpm: 1.01, - creativeId: creativeId, - currency: 'USD', - height: 250, - mediaType: 'banner', - requestId: requestId, - size: '300x250', - source: 'client', - status: 'rendered', - statusMessage: 'Bid available', - timeToRespond: 421, - ttl: 60, - width: 300 - }; - let expectedBidWonBody = { - sovrnId: 123, - payload: 'winner' - }; - let expectedWinningBid = { - bidderCode: bidderCode, - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: 'adId', - mediaType: 'banner', - source: 'client', - requestId: requestId, - cpm: 1.01, - creativeId: creativeId, - currency: 'USD', - ttl: 60, - auctionId: auctionId, - bidder: bidderCode, - adUnitCode: adUnitCode, - timeToRespond: 421, - size: '300x250', - }; - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics(); - sovrnAnalyticsAdapter.track.restore(); - }); - it('should send bid won data ', function () { - emitEvent('AUCTION_INIT', auctionInit, auctionId); - emitEvent('BID_WON', bidWonEvent, auctionId); - let requestBody = JSON.parse(server.requests[0].requestBody); - expect(requestBody).to.deep.include(expectedBidWonBody); - expect(requestBody.winningBid).to.deep.include(expectedWinningBid); - }); - }); - describe('Error Tracking', function() { - beforeEach(() => { - sovrnAnalyticsAdapter.enableAnalytics({ - provider: 'sovrn', - options: { - sovrnId: 123 - } - }); - sinon.spy(sovrnAnalyticsAdapter, 'track'); - }); - afterEach(() => { - sovrnAnalyticsAdapter.disableAnalytics() - sovrnAnalyticsAdapter.track.restore() - }); - it('should send an error message when a bid is received for a closed auction', function() { - let auctionId = '678.678.678.678'; - emitEvent('AUCTION_INIT', auctionInit, auctionId) - emitEvent('BID_REQUESTED', bidRequested, auctionId) - emitEvent('AUCTION_END', {}, auctionId) - server.requests[0].respond(200) - emitEvent('BID_RESPONSE', bidResponse, auctionId) - let requestBody = JSON.parse(server.requests[1].requestBody) - expect(requestBody.payload).to.equal('error') - expect(requestBody.message).to.include('Event Received after Auction Close Auction Id') - }) - }) -}) diff --git a/test/spec/modules/sovrnBidAdapter_spec.js b/test/spec/modules/sovrnBidAdapter_spec.js index 10f5ab8e89d..c684f8691aa 100644 --- a/test/spec/modules/sovrnBidAdapter_spec.js +++ b/test/spec/modules/sovrnBidAdapter_spec.js @@ -243,7 +243,7 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, should send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: {enabled: true} } const bidRequest = { ...baseBidRequest, @@ -273,7 +273,9 @@ describe('sovrnBidAdapter', function() { it('when FLEDGE is enabled, but env is malformed, should not send ortb2imp.ext.ae', function () { const bidderRequest = { ...baseBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } } const bidRequest = { ...baseBidRequest, @@ -419,6 +421,31 @@ describe('sovrnBidAdapter', function() { expect(regs.gpp_sid).to.include(8) }) + it('should add ORTB2 device data to the request', function () { + const bidderRequest = { + ...baseBidderRequest, + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + }, + }, + }; + + const request = spec.buildRequests([baseBidRequest], bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(bidderRequest.ortb2.device); + }); + it('should not send gpp info when gppConsent is not defined', function () { const bidderRequest = { ...baseBidderRequest, @@ -968,9 +995,9 @@ describe('sovrnBidAdapter', function() { it('should return valid fledge auction configs alongside bids', function () { const result = spec.interpretResponse(fledgeResponse) expect(result).to.have.property('bids') - expect(result).to.have.property('fledgeAuctionConfigs') - expect(result.fledgeAuctionConfigs.length).to.equal(2) - expect(result.fledgeAuctionConfigs).to.deep.equal(expectedFledgeResponse) + expect(result).to.have.property('paapi') + expect(result.paapi.length).to.equal(2) + expect(result.paapi).to.deep.equal(expectedFledgeResponse) }) it('should ignore empty fledge auction configs array', function () { const result = spec.interpretResponse(emptyFledgeResponse) diff --git a/test/spec/modules/sparteoBidAdapter_spec.js b/test/spec/modules/sparteoBidAdapter_spec.js index 293f7da30a1..ec90d4c7eeb 100644 --- a/test/spec/modules/sparteoBidAdapter_spec.js +++ b/test/spec/modules/sparteoBidAdapter_spec.js @@ -59,6 +59,7 @@ const VALID_REQUEST_BANNER = { url: REQUEST_URL, data: { 'imp': [{ + 'secure': 1, 'id': '1a2b3c4d', 'banner': { 'format': [{ @@ -71,6 +72,7 @@ const VALID_REQUEST_BANNER = { 'sparteo': { 'params': { 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-1234', 'formats': ['corner'] } } @@ -80,7 +82,8 @@ const VALID_REQUEST_BANNER = { 'publisher': { 'ext': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'pbjsVersion': '$prebid.version$' } } } @@ -94,6 +97,7 @@ const VALID_REQUEST_VIDEO = { url: REQUEST_URL, data: { 'imp': [{ + 'secure': 1, 'id': '5e6f7g8h', 'video': { 'w': 640, @@ -112,7 +116,8 @@ const VALID_REQUEST_VIDEO = { 'pbadslot': 'video', 'sparteo': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-5678' } } } @@ -121,7 +126,8 @@ const VALID_REQUEST_VIDEO = { 'publisher': { 'ext': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'pbjsVersion': '$prebid.version$' } } } @@ -135,6 +141,7 @@ const VALID_REQUEST = { url: REQUEST_URL, data: { 'imp': [{ + 'secure': 1, 'id': '1a2b3c4d', 'banner': { 'format': [{ @@ -147,11 +154,13 @@ const VALID_REQUEST = { 'sparteo': { 'params': { 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-1234', 'formats': ['corner'] } } } }, { + 'secure': 1, 'id': '5e6f7g8h', 'video': { 'w': 640, @@ -170,7 +179,8 @@ const VALID_REQUEST = { 'pbadslot': 'video', 'sparteo': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'adUnitCode': 'id-5678' } } } @@ -179,7 +189,8 @@ const VALID_REQUEST = { 'publisher': { 'ext': { 'params': { - 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf' + 'networkId': '1234567a-eb1b-1fae-1d23-e1fbaef234cf', + 'pbjsVersion': '$prebid.version$' } } } diff --git a/test/spec/modules/spotxBidAdapter_spec.js b/test/spec/modules/spotxBidAdapter_spec.js deleted file mode 100644 index ec99d0f7142..00000000000 --- a/test/spec/modules/spotxBidAdapter_spec.js +++ /dev/null @@ -1,711 +0,0 @@ -import {expect} from 'chai'; -import {config} from 'src/config.js'; -import {loadExternalScript} from '../../../src/adloader'; -import {isRendererRequired} from '../../../src/Renderer'; -import {spec, GOOGLE_CONSENT} from 'modules/spotxBidAdapter.js'; - -describe('the spotx adapter', function () { - function getValidBidObject() { - return { - bidId: 123, - mediaTypes: { - video: { - playerSize: [['300', '200']] - } - }, - params: { - channel_id: 12345, - } - }; - }; - - describe('isBidRequestValid', function() { - let bid; - - beforeEach(function() { - bid = getValidBidObject(); - }); - - it('should fail validation if the bid isn\'t defined or not an object', function() { - let result = spec.isBidRequestValid(); - - expect(result).to.equal(false); - - result = spec.isBidRequestValid('not an object'); - - expect(result).to.equal(false); - }); - - it('should succeed validation with all the right parameters', function() { - expect(spec.isBidRequestValid(getValidBidObject())).to.equal(true); - }); - - it('should succeed validation with mediaType and outstream_function or outstream_options', function() { - bid.mediaType = 'video'; - bid.params.outstream_function = 'outstream_func'; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - - delete bid.params.outstream_function; - bid.params.outstream_options = { - slot: 'elemID' - }; - - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should succeed with ad_unit outstream and outstream function set', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_function = function() {}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should succeed with ad_unit outstream, options set for outstream and slot provided', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_options = {slot: 'ad_container_id'}; - expect(spec.isBidRequestValid(bid)).to.equal(true); - }); - - it('should fail without a channel_id', function() { - delete bid.params.channel_id; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail without playerSize', function() { - delete bid.mediaTypes.video.playerSize; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail without video', function() { - delete bid.mediaTypes.video; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail with ad_unit outstream but no options set for outstream', function() { - bid.params.ad_unit = 'outstream'; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - - it('should fail with ad_unit outstream, options set for outstream but no slot provided', function() { - bid.params.ad_unit = 'outstream'; - bid.params.outstream_options = {}; - expect(spec.isBidRequestValid(bid)).to.equal(false); - }); - }); - - describe('buildRequests', function() { - let bid, bidRequestObj; - - beforeEach(function() { - bid = getValidBidObject(); - bidRequestObj = { - refererInfo: { - page: 'prebid.js' - } - }; - }); - - it('should build a very basic request', function() { - let request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.method).to.equal('POST'); - expect(request.url).to.equal('https://search.spotxchange.com/openrtb/2.3/dados/12345?src_sys=prebid'); - expect(request.bidRequest).to.equal(bidRequestObj); - expect(request.data.id).to.equal(12345); - expect(request.data.ext.wrap_response).to.equal(1); - expect(request.data.imp.id).to.match(/\d+/); - expect(request.data.imp.secure).to.equal(0); - expect(request.data.imp.video).to.deep.equal({ - ext: { - sdk_name: 'Prebid 1+', - versionOrtb: '2.3' - }, - h: '200', - mimes: [ - 'application/javascript', - 'video/mp4', - 'video/webm' - ], - w: '300' - }); - expect(request.data.site).to.deep.equal({ - content: 'content', - id: '', - page: 'prebid.js' - }); - }); - - it('should change request parameters based on options sent', function() { - let request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext).to.deep.equal({ - sdk_name: 'Prebid 1+', - versionOrtb: '2.3' - }); - - bid.params = { - channel_id: 54321, - ad_mute: 1, - hide_skin: 1, - ad_volume: 1, - ad_unit: 'incontent', - outstream_options: {foo: 'bar'}, - outstream_function: '987', - custom: {bar: 'foo'}, - start_delay: true, - number_of_ads: 2, - spotx_all_google_consent: 1, - min_duration: 5, - max_duration: 10, - placement_type: 1, - position: 1 - }; - - bid.userIdAsEids = [{ - source: 'adserver.org', - uids: [{id: 'tdid_1', atype: 1, ext: {rtiPartner: 'TDID'}}] - }, - { - source: 'id5-sync.com', - uids: [{id: 'id5id_1', ext: {}}] - }, - { - source: 'uidapi.com', - uids: [{ - id: 'uid_1', - atype: 3 - }] - } - ]; - - bid.crumbs = { - pubcid: 'pubcid_1' - }; - - bid.schain = { - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.id).to.equal(54321); - expect(request.data.imp.video).to.contain({ - minduration: 5, - maxduration: 10 - }) - expect(request.data.imp.video.ext).to.deep.equal({ - ad_volume: 1, - hide_skin: 1, - ad_unit: 'incontent', - outstream_options: {foo: 'bar'}, - outstream_function: '987', - custom: {bar: 'foo'}, - sdk_name: 'Prebid 1+', - versionOrtb: '2.3', - placement: 1, - pos: 1 - }); - - expect(request.data.imp.video.startdelay).to.equal(1); - expect(request.data.ext).to.deep.equal({ - number_of_ads: 2, - wrap_response: 1 - }); - expect(request.data.user.ext).to.deep.equal({ - consented_providers_settings: GOOGLE_CONSENT, - eids: [{ - source: 'adserver.org', - uids: [{ - id: 'tdid_1', - atype: 1, - ext: { - rtiPartner: 'TDID' - } - }] - }, { - source: 'id5-sync.com', - uids: [{ - id: 'id5id_1', - ext: {} - }] - }, - { - source: 'uidapi.com', - uids: [{ - id: 'uid_1', - atype: 3, - ext: { - rtiPartner: 'UID2' - } - }] - }], - fpc: 'pubcid_1' - }); - - expect(request.data.source).to.deep.equal({ - ext: { - schain: { - complete: 1, - nodes: [ - { - asi: 'indirectseller.com', - sid: '00001', - hp: 1 - } - ] - } - } - }) - }); - - it('should process premarket bids', function() { - let request; - sinon.stub(Date, 'now').returns(1000); - - bid.params.pre_market_bids = [{ - vast_url: 'prebid.js', - deal_id: '123abc', - price: 12, - currency: 'USD' - }]; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.imp.video.ext.pre_market_bids).to.deep.equal([ - { - 'cur': 'USD', - 'ext': { - 'event_log': [ - {} - ] - }, - 'id': '123abc', - 'seatbid': [ - { - 'bid': [ - { - 'adm': 'prebid.js', - 'dealid': '123abc', - 'impid': 1000, - 'price': 12, - } - ] - } - ] - } - ]); - Date.now.restore(); - }); - - it('should pass GDPR params', function() { - let request; - - bidRequestObj.gdprConsent = { - consentString: 'consent123', - gdprApplies: true - }; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.regs.ext.gdpr).to.equal(1); - expect(request.data.user.ext.consent).to.equal('consent123'); - }); - - it('should pass CCPA us_privacy string', function() { - let request; - - bidRequestObj.uspConsent = '1YYY' - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('should pass both GDPR params and CCPA us_privacy', function() { - let request; - - bidRequestObj.gdprConsent = { - consentString: 'consent123', - gdprApplies: true - }; - bidRequestObj.uspConsent = '1YYY' - - request = spec.buildRequests([bid], bidRequestObj)[0]; - expect(request.data.regs.ext.gdpr).to.equal(1); - expect(request.data.user.ext.consent).to.equal('consent123'); - expect(request.data.regs.ext.us_privacy).to.equal('1YYY'); - }); - - it('should pass min and max duration params', function() { - let request; - - bid.params.min_duration = 3 - bid.params.max_duration = 15 - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.video.minduration).to.equal(3); - expect(request.data.imp.video.maxduration).to.equal(15); - }); - - it('should pass placement_type and position params', function() { - let request; - - bid.params.placement_type = 2 - bid.params.position = 5 - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.video.ext.placement).to.equal(2); - expect(request.data.imp.video.ext.pos).to.equal(5); - }); - - it('should pass page param and override refererInfo.referer', function() { - let request; - - bid.params.page = 'https://example.com'; - - let origGetConfig = config.getConfig; - sinon.stub(config, 'getConfig').callsFake(function (key) { - if (key === 'pageUrl') { - return 'https://www.spotx.tv'; - } - return origGetConfig.apply(config, arguments); - }); - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.site.page).to.equal('https://example.com'); - config.getConfig.restore(); - }); - - it('should use refererInfo.referer if no page is passed', function() { - let request; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.site.page).to.equal('prebid.js'); - }); - - it('should set ext.wrap_response to 0 when cache url is set and ignoreBidderCacheKey is true', function() { - let request; - - let origGetConfig = config.getConfig; - sinon.stub(config, 'getConfig').callsFake(function (key) { - if (key === 'cache') { - return { - url: 'prebidCacheLocation', - ignoreBidderCacheKey: true - }; - } - if (key === 'cache.url') { - return 'prebidCacheLocation'; - } - if (key === 'cache.ignoreBidderCacheKey') { - return true; - } - return origGetConfig.apply(config, arguments); - }); - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.ext.wrap_response).to.equal(0); - config.getConfig.restore(); - }); - - it('should pass price floor in USD from the floors module if available', function () { - let request; - - bid.getFloor = function () { - return { currency: 'USD', floor: 3 }; - } - - bid.params.price_floor = 2; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.equal(3); - }); - - it('should not pass price floor if price floors module gives a non-USD currency', function () { - let request; - - bid.getFloor = function () { - return { currency: 'EUR', floor: 3 }; - } - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.be.undefined; - }); - - it('if floors module is not available, should pass price floor from price_floor param if available', function () { - let request; - - bid.params.price_floor = 2; - - request = spec.buildRequests([bid], bidRequestObj)[0]; - - expect(request.data.imp.bidfloor).to.equal(2); - }); - }); - - describe('interpretResponse', function() { - let serverResponse, bidderRequestObj; - - beforeEach(function() { - bidderRequestObj = { - bidRequest: { - bids: [{ - mediaTypes: { - video: { - playerSize: [['400', '300']] - } - }, - bidId: 123, - params: { - ad_unit: 'outstream', - player_width: 400, - player_height: 300, - content_page_url: 'prebid.js', - ad_mute: 1, - outstream_options: {foo: 'bar'}, - outstream_function: 'function' - } - }, { - mediaTypes: { - video: { - playerSize: [['200', '100']] - } - }, - bidId: 124, - params: { - player_width: 200, - player_height: 100, - content_page_url: 'prebid.js', - ad_mute: 1, - outstream_options: {foo: 'bar'}, - outstream_function: 'function' - } - }] - } - }; - - serverResponse = { - body: { - id: 12345, - seatbid: [{ - bid: [{ - impid: 123, - cur: 'USD', - price: 12, - adomain: ['abc.com'], - crid: 321, - w: 400, - h: 300, - ext: { - cache_key: 'cache123', - slot: 'slot123' - } - }, { - impid: 124, - cur: 'USD', - price: 13, - adomain: ['def.com'], - w: 200, - h: 100, - ext: { - cache_key: 'cache124', - slot: 'slot124' - } - }] - }] - } - }; - }); - - it('should return an array of bid responses', function() { - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - expect(responses).to.be.an('array').with.length(2); - expect(responses[0].cache_key).to.equal('cache123'); - expect(responses[0].channel_id).to.equal(12345); - expect(responses[0].meta.advertiserDomains[0]).to.equal('abc.com'); - expect(responses[0].cpm).to.equal(12); - expect(responses[0].creativeId).to.equal(321); - expect(responses[0].currency).to.equal('USD'); - expect(responses[0].height).to.equal(300); - expect(responses[0].mediaType).to.equal('video'); - expect(responses[0].netRevenue).to.equal(true); - expect(responses[0].requestId).to.equal(123); - expect(responses[0].ttl).to.equal(360); - expect(responses[0].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(responses[0].videoCacheKey).to.equal('cache123'); - expect(responses[0].width).to.equal(400); - expect(responses[1].cache_key).to.equal('cache124'); - expect(responses[1].channel_id).to.equal(12345); - expect(responses[1].cpm).to.equal(13); - expect(responses[1].meta.advertiserDomains[0]).to.equal('def.com'); - expect(responses[1].creativeId).to.equal(''); - expect(responses[1].currency).to.equal('USD'); - expect(responses[1].height).to.equal(100); - expect(responses[1].mediaType).to.equal('video'); - expect(responses[1].netRevenue).to.equal(true); - expect(responses[1].requestId).to.equal(124); - expect(responses[1].ttl).to.equal(360); - expect(responses[1].vastUrl).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache124'); - expect(responses[1].videoCacheKey).to.equal('cache124'); - expect(responses[1].width).to.equal(200); - }); - - it('should set the renderer attached to the bid to render immediately', function () { - var renderer = spec.interpretResponse(serverResponse, bidderRequestObj)[0].renderer, - hasRun = false; - expect(renderer._render).to.be.a('function'); - renderer._render = () => { - hasRun = true; - } - renderer.render(); - expect(hasRun).to.equal(true); - }); - - it('should include the url property on the renderer for Prebid Core checks', function () { - var renderer = spec.interpretResponse(serverResponse, bidderRequestObj)[0].renderer; - expect(isRendererRequired(renderer)).to.be.true; - }); - }); - - describe('outstreamRender', function() { - let serverResponse, bidderRequestObj; - - beforeEach(function() { - sinon.stub(window.document, 'getElementById').returns({ - clientWidth: 200, - appendChild: sinon.stub().callsFake(function(script) {}) - }); - sinon.stub(window.document, 'createElement').returns({ - setAttribute: function () {} - }); - bidderRequestObj = { - bidRequest: { - bids: [{ - mediaTypes: { - video: { - playerSize: [['400', '300']] - } - }, - bidId: 123, - params: { - ad_unit: 'outstream', - player_width: 400, - player_height: 300, - content_page_url: 'prebid.js', - outstream_options: { - ad_mute: 1, - foo: 'bar', - slot: 'slot123', - playersize_auto_adapt: true, - custom_override: { - digitrust_opt_out: 1, - vast_url: 'bad_vast' - } - }, - } - }] - } - }; - - serverResponse = { - body: { - id: 12345, - seatbid: [{ - bid: [{ - impid: 123, - cur: 'USD', - price: 12, - crid: 321, - w: 400, - h: 300, - ext: { - cache_key: 'cache123', - slot: 'slot123' - } - }] - }] - } - }; - }); - afterEach(function () { - window.document.getElementById.restore(); - window.document.createElement.restore(); - }); - - it('should attempt to insert the EASI script', function() { - window.document.getElementById.restore(); - sinon.stub(window.document, 'getElementById').returns({ - appendChild: sinon.stub().callsFake(function(script) {}), - }); - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - let attrs; - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.called).to.be.true; - attrs = valuesToString(loadExternalScript.args[0][4]); - - expect(attrs['data-spotx_channel_id']).to.equal('12345'); - expect(attrs['data-spotx_vast_url']).to.equal('https://search.spotxchange.com/ad/vast.html?key=cache123'); - expect(attrs['data-spotx_ad_unit']).to.equal('incontent'); - expect(attrs['data-spotx_collapse']).to.equal('0'); - expect(attrs['data-spotx_autoplay']).to.equal('1'); - expect(attrs['data-spotx_blocked_autoplay_override_mode']).to.equal('1'); - expect(attrs['data-spotx_video_slot_can_autoplay']).to.equal('1'); - expect(attrs['data-spotx_digitrust_opt_out']).to.equal('1'); - expect(attrs['data-spotx_content_width']).to.equal('400'); - expect(attrs['data-spotx_content_height']).to.equal('300'); - expect(attrs['data-spotx_ad_mute']).to.equal('1'); - }); - - it('should append into an iframe', function() { - bidderRequestObj.bidRequest.bids[0].params.outstream_options.in_iframe = 'iframeId'; - window.document.getElementById.restore(); - sinon.stub(window.document, 'getElementById').returns({ - nodeName: 'IFRAME', - clientWidth: 200, - appendChild: sinon.stub().callsFake(function(script) {}), - contentDocument: {nodeName: 'IFRAME'} - }); - - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.called).to.be.true; - expect(loadExternalScript.args[0][3].nodeName).to.equal('IFRAME'); - }); - - it('should adjust width and height to match slot clientWidth if playersize_auto_adapt is used', function() { - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.args[0][4]['data-spotx_content_width']).to.equal('200'); - expect(loadExternalScript.args[0][4]['data-spotx_content_height']).to.equal('150'); - }); - - it('should use a default 4/3 ratio if playersize_auto_adapt is used and response does not contain width or height', function() { - delete serverResponse.body.seatbid[0].bid[0].w; - delete serverResponse.body.seatbid[0].bid[0].h; - let responses = spec.interpretResponse(serverResponse, bidderRequestObj); - - responses[0].renderer.render(responses[0]); - expect(loadExternalScript.args[0][4]['data-spotx_content_width']).to.equal('200'); - expect(loadExternalScript.args[0][4]['data-spotx_content_height']).to.equal('150'); - }); - }); -}); - -function valuesToString(obj) { - let newObj = {}; - for (let prop in obj) { - newObj[prop] = '' + obj[prop]; - } - return newObj; -} diff --git a/test/spec/modules/sspBCBidAdapter_spec.js b/test/spec/modules/sspBCBidAdapter_spec.js index 2f5fe104eb1..623faab5f1e 100644 --- a/test/spec/modules/sspBCBidAdapter_spec.js +++ b/test/spec/modules/sspBCBidAdapter_spec.js @@ -303,7 +303,7 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE1', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -319,7 +319,7 @@ describe('SSPBC adapter', function () { 'siteid': '8816', 'slotid': '005', 'price': 2, - 'adm': 'AD CODE 2', + 'adm': 'AD_CODE2', 'cid': '57744', 'crid': '858252', 'w': 300, @@ -343,7 +343,64 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE', + 'adomain': ['adomain.pl'], + 'cid': 'BZ4gAg21T5nNtxlUCDSW', + 'crid': 'lxHWkB7OnZeso3QiN1N4', + 'w': 728, + 'h': 90, + }], + 'seat': 'dsp1', + 'group': 0 + }], + 'cur': 'PLN' + } + }; + + const serverResponsePaapi = { + 'body': { + 'id': bidderRequestId, + 'seatbid': [{ + 'bid': [{ + 'id': '3347324c-6889-46d2-a800-ae78a5214c06', + 'impid': '003', + 'siteid': '8816', + 'slotid': '003', + 'price': 1, + 'adid': 'lxHWkB7OnZeso3QiN1N4', + 'nurl': '', + 'adm': 'AD_CODE', + 'adomain': ['adomain.pl'], + 'cid': 'BZ4gAg21T5nNtxlUCDSW', + 'crid': 'lxHWkB7OnZeso3QiN1N4', + 'w': 728, + 'h': 90, + }], + 'seat': 'dsp1', + 'group': 0 + }], + 'cur': 'PLN', + 'ext': { + 'paapi': [ + { 'config_data': 'config value' }, + ] + }, + } + }; + + const serverResponseIncorrect = { + 'body': { + 'id': bidderRequestId, + 'seatbid': [{ + 'bid': [{ + 'id': '3347324c-6889-46d2-a800-ae78a5214c06', + 'impid': '003', + 'siteid': '8816', + 'slotid': '003', + 'price': 1, + 'adid': 'lxHWkB7OnZeso3QiN1N4', + 'nurl': '', + 'adm': 'THIS_IS_NOT_AN_AD', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -366,7 +423,7 @@ describe('SSPBC adapter', function () { 'price': 1, 'adid': 'lxHWkB7OnZeso3QiN1N4', 'nurl': '', - 'adm': 'AD CODE 1', + 'adm': 'AD_CODE', 'adomain': ['adomain.pl'], 'cid': 'BZ4gAg21T5nNtxlUCDSW', 'crid': 'lxHWkB7OnZeso3QiN1N4', @@ -458,6 +515,8 @@ describe('SSPBC adapter', function () { serverResponse, serverResponseOneCode, serverResponseSingle, + serverResponseIncorrect, + serverResponsePaapi, serverResponseVideo, serverResponseNative, emptyResponse @@ -593,7 +652,7 @@ describe('SSPBC adapter', function () { }); describe('interpretResponse', function () { - const { bid_OneCode, bid_video, bid_native, bids, emptyResponse, serverResponse, serverResponseOneCode, serverResponseSingle, serverResponseVideo, serverResponseNative, bidRequest, bidRequestOneCode, bidRequestSingle, bidRequestVideo, bidRequestNative } = prepareTestData(); + const { bid_OneCode, bid_video, bid_native, bids, emptyResponse, serverResponse, serverResponseOneCode, serverResponseSingle, serverResponseIncorrect, serverResponsePaapi, serverResponseVideo, serverResponseNative, bidRequest, bidRequestOneCode, bidRequestSingle, bidRequestVideo, bidRequestNative } = prepareTestData(); const request = spec.buildRequests(bids, bidRequest); const requestSingle = spec.buildRequests([bids[0]], bidRequestSingle); const requestOneCode = spec.buildRequests([bid_OneCode], bidRequestOneCode); @@ -632,15 +691,11 @@ describe('SSPBC adapter', function () { expect(resultPartial.length).to.equal(1); }); - it('banner ad code should contain required variables', function () { + it('should not alter HTML from response', function () { let resultSingle = spec.interpretResponse(serverResponseSingle, requestSingle); let adcode = resultSingle[0].ad; - expect(adcode).to.be.a('string'); - expect(adcode).to.contain('window.rekid'); - expect(adcode).to.contain('window.mcad'); - expect(adcode).to.contain('window.tcString'); - expect(adcode).to.contain('window.page'); - expect(adcode).to.contain('window.requestPVID'); + + expect(adcode).to.be.equal(serverResponseSingle.body.seatbid[0].bid[0].adm); }); it('should create a correct video bid', function () { @@ -666,6 +721,19 @@ describe('SSPBC adapter', function () { expect(nativeBid).to.have.keys('cpm', 'creativeId', 'currency', 'width', 'height', 'meta', 'mediaType', 'netRevenue', 'requestId', 'ttl', 'native', 'vurls'); expect(nativeBid.native).to.have.keys('image', 'icon', 'title', 'sponsoredBy', 'body', 'clickUrl', 'impressionTrackers', 'javascriptTrackers', 'clickTrackers'); }); + + it('should reject responses that are not HTML, VATS/VPAID or native', function () { + let resultIncorrect = spec.interpretResponse(serverResponseIncorrect, requestSingle); + + expect(resultIncorrect.length).to.equal(0); + }); + + it('should response with fledge auction configs', function () { + const { bids, fledgeAuctionConfigs } = spec.interpretResponse(serverResponsePaapi, requestSingle); + + expect(bids.length).to.equal(1); + expect(fledgeAuctionConfigs.length).to.equal(1); + }); }); describe('getUserSyncs', function () { @@ -703,6 +771,25 @@ describe('SSPBC adapter', function () { }); }); + describe('onBidBillable', function () { + it('should generate no notification if bid is undefined', function () { + let notificationPayload = spec.onBidBillable(); + expect(notificationPayload).to.be.undefined; + }); + + it('should generate notification with event name and request/adUnit data, if correct bid is provided. Should also contain site/slot data as arrays.', function () { + const { bids } = prepareTestData(); + let bid = bids[0]; + + let notificationPayload = spec.onBidBillable(bid); + expect(notificationPayload).to.have.property('event').that.equals('bidBillable'); + expect(notificationPayload).to.have.property('requestId').that.equals(bid.bidderRequestId); + expect(notificationPayload).to.have.property('tagid').that.deep.equals([bid.adUnitCode]); + expect(notificationPayload).to.have.property('siteId').that.is.an('array'); + expect(notificationPayload).to.have.property('slotId').that.is.an('array'); + }); + }); + describe('onTimeout', function () { it('should generate no notification if timeout data is undefined / has no bids', function () { let notificationPayloadUndefined = spec.onTimeout(); diff --git a/test/spec/modules/ssp_genieeBidAdapter_spec.js b/test/spec/modules/ssp_genieeBidAdapter_spec.js new file mode 100644 index 00000000000..dbf8ded5199 --- /dev/null +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -0,0 +1,401 @@ +import { expect } from 'chai'; +import { + spec, + BANNER_ENDPOINT, +} from 'modules/ssp_genieeBidAdapter.js'; +import { config } from 'src/config.js'; + +describe('ssp_genieeBidAdapter', function () { + const ZONE_ID = 1234567; + const AD_UNIT_CODE = 'adunit-code'; + const BANNER_BID = { + bidder: spec.code, + params: { + zoneId: ZONE_ID, + invalidImpBeacon: false, + }, + adUnitCode: AD_UNIT_CODE, + sizes: [[300, 250]], + bidId: 'bidId12345', + bidderRequestId: 'bidderRequestId12345', + auctionId: 'auctionId12345', + }; + + function getGeparamsDefinedBid(bid, params) { + const newBid = { ...bid }; + newBid.params.geparams = params; + return newBid; + } + + function hasParamsNotBlankStringTestGeparams(param, query) { + it(`should set the ${query} query to geparams.${param} when geparams.${param} is neither undefined nor null nor a blank string`, function () { + window.geparams[param] = undefined; + let request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.geparams[param] = null; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.geparams[param] = ''; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + const value = 'hoge'; + window.geparams[param] = value; + request = spec.buildRequests([BANNER_BID]); + expect(JSON.stringify(request[0].data)).to.have.string(`"${query}":"${value}"`); + }); + } + + function hasParamsNotBlankStringTestGecuparams(param, query) { + it(`should set the ${query} query to gecuparams.${param} when gecuparams.${param} is neither undefined nor null nor a blank string`, function () { + window.gecuparams = {}; + window.gecuparams[param] = undefined; + let request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.gecuparams[param] = null; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + window.gecuparams[param] = ''; + request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property(`"${query}:`); + + const value = 'hoge'; + window.gecuparams[param] = value; + request = spec.buildRequests([BANNER_BID]); + expect(JSON.stringify(request[0].data)).to.have.string(`"${query}":"${value}"`); + }); + } + + beforeEach(function () { + document.documentElement.innerHTML = ''; + const adTagParent = document.createElement('div'); + adTagParent.id = AD_UNIT_CODE; + document.body.appendChild(adTagParent); + }); + + describe('isBidRequestValid', function () { + it('should return true when params.zoneId exists and params.currency does not exist', function () { + expect(spec.isBidRequestValid(BANNER_BID)).to.be.true; + }); + + it('should return true when params.zoneId and params.currency exist and params.currency is JPY or USD', function () { + config.setConfig({ currency: { adServerCurrency: 'JPY' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.true; + config.setConfig({ currency: { adServerCurrency: 'USD' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.true; + }); + + it('should return false when params.zoneId does not exist', function () { + expect(spec.isBidRequestValid({ ...BANNER_BID, params: {} })).to.be.false; + }); + + it('should return false when params.zoneId and params.currency exist and params.currency is neither JPY nor USD', function () { + config.setConfig({ currency: { adServerCurrency: 'EUR' } }); + expect( + spec.isBidRequestValid({ + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }) + ).to.be.false; + }); + }); + + describe('buildRequests', function () { + it('should changes the endpoint with banner ads or naive ads', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].url).to.equal(BANNER_ENDPOINT); + }); + + it('should return a ServerRequest where the bid is a bid for validBidRequests', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].bid).to.equal(BANNER_BID); + }); + + describe('QueryStringParameters', function () { + it('should sets the value of the zoneid query to bid.params.zoneId', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data.zoneid).to.deep.equal(BANNER_BID.params.zoneId); + }); + + it('should sets the values for loc and referer queries when bidderRequest.refererInfo.referer has a value', function () { + const referer = 'https://example.com/'; + const request = spec.buildRequests([BANNER_BID], { + refererInfo: { legacy: { referer: referer }, ref: referer }, + }); + expect(request[0].data.loc).to.deep.equal(referer); + expect(request[0].data.referer).to.deep.equal(referer); + }); + + it('should makes the values of loc query and referer query geparams value when bidderRequest.refererInfo.referer is a falsy value', function () { + const loc = 'https://www.google.com/'; + const referer = 'https://example.com/'; + window.geparams = { + loc: 'https://www.google.com/', + ref: 'https://example.com/', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { loc: loc, ref: referer }), + ]); + expect(request[0].data.loc).to.deep.equal(encodeURIComponent(loc)); + expect(request[0].data.referer).to.deep.equal(encodeURIComponent(referer)); + }); + + it('should sets the value of the ct0 query to geparams.ct0', function () { + const ct0 = 'hoge'; + window.geparams = { + ct0: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { ct0: ct0 }), + ]); + expect(request[0].data.ct0).to.deep.equal(ct0); + }); + + it('should replaces currency with JPY if there is no currency provided', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data.cur).to.deep.equal('JPY'); + }); + + it('should makes currency the value of params.currency when params.currency exists', function () { + const request = spec.buildRequests([ + { + ...BANNER_BID, + params: { ...BANNER_BID.params, currency: 'JPY' }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params, currency: 'USD' }, + }, + ]); + expect(request[0].data.cur).to.deep.equal('JPY'); + expect(request[1].data.cur).to.deep.equal('USD'); + }); + + it('should makes invalidImpBeacon the value of params.invalidImpBeacon when params.invalidImpBeacon exists (in current version, this parameter is not necessary and ib is always `0`)', function () { + const request = spec.buildRequests([ + { + ...BANNER_BID, + params: { ...BANNER_BID.params, invalidImpBeacon: true }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params, invalidImpBeacon: false }, + }, + { + ...BANNER_BID, + params: { ...BANNER_BID.params }, + }, + ]); + expect(request[0].data.ib).to.deep.equal(0); + expect(request[1].data.ib).to.deep.equal(0); + expect(request[2].data.ib).to.deep.equal(0); + }); + + it('should not sets the value of the adtk query when geparams.lat does not exist', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('adtk'); + }); + + it('should sets the value of the adtk query to 0 when geparams.lat is truthy value', function () { + window.geparams = { + lat: 1, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { lat: 1 }), + ]); + expect(request[0].data.adtk).to.deep.equal('0'); + }); + + it('should sets the value of the adtk query to 1 when geparams.lat is falsy value', function () { + window.geparams = { + lat: 0, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { lat: 0 }), + ]); + expect(request[0].data.adtk).to.deep.equal('1'); + }); + + it('should sets the value of the idfa query to geparams.idfa', function () { + const idfa = 'hoge'; + window.geparams = { + idfa: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { idfa: idfa }), + ]); + expect(request[0].data.idfa).to.deep.equal(idfa); + }); + + it('should set the sw query to screen.height and the sh query to screen.width when screen.width is greater than screen.height', function () { + const width = 1440; + const height = 900; + const stub = sinon.stub(window, 'screen').get(function () { + return { width: width, height: height }; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data.sw).to.deep.equal(height); + expect(request[0].data.sh).to.deep.equal(width); + stub.restore(); + }); + + it('should set the sw query to screen.width and the sh query to screen.height when screen.width is not greater than screen.height', function () { + const width = 411; + const height = 731; + const stub = sinon.stub(window, 'screen').get(function () { + return { width: width, height: height }; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data.sw).to.deep.equal(width); + expect(request[0].data.sh).to.deep.equal(height); + stub.restore(); + }); + + hasParamsNotBlankStringTestGeparams('zip', 'zip'); + hasParamsNotBlankStringTestGeparams('country', 'country'); + hasParamsNotBlankStringTestGeparams('city', 'city'); + hasParamsNotBlankStringTestGeparams('long', 'long'); + hasParamsNotBlankStringTestGeparams('lati', 'lati'); + + it('should set the custom query to geparams.custom', function () { + const params = { + custom: { + c1: undefined, + c2: null, + c3: '', + c4: 'hoge', + }, + }; + window.geparams = { + custom: { + c1: undefined, + c2: null, + c3: '', + c4: 'hoge', + }, + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, params), + ]); + expect(request[0].data).to.not.have.property('custom_c1'); + expect(request[0].data).to.not.have.property('custom_c2'); + expect(request[0].data).to.not.have.property('custom_c3'); + expect(request[0].data.custom_c4).to.have.string( + `${params.custom.c4}` + ); + }); + + hasParamsNotBlankStringTestGecuparams('ver', 'gc_ver'); + hasParamsNotBlankStringTestGecuparams('minor', 'gc_minor'); + hasParamsNotBlankStringTestGecuparams('value', 'gc_value'); + + it('should sets the value of the gfuid query to geparams.gfuid', function () { + const gfuid = 'hoge'; + window.geparams = { + gfuid: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { gfuid: gfuid }), + ]); + expect(request[0].data).to.not.have.property('gfuid'); + }); + + it('should sets the value of the adt query to geparams.adt', function () { + const adt = 'hoge'; + window.geparams = { + adt: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { adt: adt }), + ]); + expect(request[0].data).to.not.have.property('adt'); + }); + + it('should adds a query for naive ads and no query for banner ads', function () { + // const query = '&tkf=1&ad_track=1&apiv=1.1.0'; + const query_apiv = '1.1.0'; + const query_tkf = '1'; + const query_ad_track = '1'; + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.apiv)).to.not.have.string(query_apiv); + expect(String(request[0].data.tkf)).to.not.have.string(query_tkf); + expect(String(request[0].data.ad_track)).to.not.have.string(query_ad_track); + }); + + it('should sets the value of the apid query to geparams.bundle when media type is banner', function () { + const bundle = 'hoge'; + window.geparams = { + bundle: 'hoge', + }; + const request = spec.buildRequests([ + getGeparamsDefinedBid(BANNER_BID, { bundle: bundle }), + ]); + expect(request[0].data.apid).to.deep.equal(bundle); + }); + + it('should not include the extuid query when bid.userId.imuid does not exist', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('extuid'); + }); + + it('should include an extuid query when bid.userId.imuid exists', function () { + const imuid = 'b.a4ad1d3eeb51e600'; + const request = spec.buildRequests([{...BANNER_BID, userId: {imuid}}]); + expect(request[0].data.extuid).to.deep.equal(`im:${imuid}`); + }); + }); + }); + + describe('interpretResponse', function () { + const response = {}; + response[ZONE_ID] = { + creativeId: '', + cur: 'JPY', + price: 0.092, + width: 300, + height: 250, + requestid: '2e42361a6172bf', + adm: '', + }; + const expected = { + requestId: response[ZONE_ID].requestid, + cpm: response[ZONE_ID].price, + creativeId: response[ZONE_ID].creativeId, + netRevenue: true, + currency: 'JPY', + ttl: 700, + width: response[ZONE_ID].width, + height: response[ZONE_ID].height, + }; + + it('should sets the response correctly when it comes to banner ads', function () { + const expectedBanner = { + ...expected, + ad: + '
' + + response[ZONE_ID].adm + + '
', + mediaType: 'banner', + }; + const request = spec.buildRequests([BANNER_BID])[0]; + const result = spec.interpretResponse({ body: response }, request); + expect(result[0]).to.deep.equal(expectedBanner); + }); + }); +}); diff --git a/test/spec/modules/staqAnalyticsAdapter_spec.js b/test/spec/modules/staqAnalyticsAdapter_spec.js deleted file mode 100644 index 3f28098e1d1..00000000000 --- a/test/spec/modules/staqAnalyticsAdapter_spec.js +++ /dev/null @@ -1,302 +0,0 @@ -import analyticsAdapter, { ExpiringQueue, getUmtSource, storage } from 'modules/staqAnalyticsAdapter.js'; -import { expect } from 'chai'; -import adapterManager from 'src/adapterManager.js'; -import { EVENTS } from 'src/constants.js'; - -const events = require('../../../src/events'); - -const DIRECT = { - source: '(direct)', - medium: '(direct)', - campaign: '(direct)' -}; -const REFERRER = { - source: 'lander.com', - medium: '(referral)', - campaign: '(referral)', - content: '/lander.html' -}; -const GOOGLE_ORGANIC = { - source: 'google', - medium: '(organic)', - campaign: '(organic)' -}; -const CAMPAIGN = { - source: 'adkernel', - medium: 'email', - campaign: 'new_campaign', - c1: '1', - c2: '2', - c3: '3', - c4: '4', - c5: '5' - -}; -describe('', function() { - let sandbox; - - before(function() { - sandbox = sinon.sandbox.create(); - }); - - after(function() { - sandbox.restore(); - analyticsAdapter.disableAnalytics(); - }); - - describe('UTM source parser', function() { - let stubSetItem; - let stubGetItem; - - before(function() { - stubSetItem = sandbox.stub(storage, 'setItem'); - stubGetItem = sandbox.stub(storage, 'getItem'); - }); - - afterEach(function() { - sandbox.reset(); - }); - - it('should parse first direct visit as (direct)', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com'); - expect(source).to.be.eql(DIRECT); - }); - - it('should parse visit from google as organic', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com', 'https://www.google.com/search?q=pikachu'); - expect(source).to.be.eql(GOOGLE_ORGANIC); - }); - - it('should parse referral visit', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://example.com', 'https://lander.com/lander.html'); - expect(source).to.be.eql(REFERRER); - }); - - it('should parse referral visit from same domain as direct', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://lander.com/news.html', 'https://lander.com/lander.html'); - expect(source).to.be.eql(DIRECT); - }); - - it('should parse campaign visit', function() { - stubGetItem.withArgs('adk_dpt_analytics').returns(undefined); - stubSetItem.returns(undefined); - let source = getUmtSource('https://lander.com/index.html?utm_campaign=new_campaign&utm_source=adkernel&utm_medium=email&utm_c1=1&utm_c2=2&utm_c3=3&utm_c4=4&utm_c5=5'); - expect(source).to.be.eql(CAMPAIGN); - }); - }); - - describe('ExpiringQueue', function() { - let timer; - before(function() { - timer = sandbox.useFakeTimers(0); - }); - after(function() { - timer.restore(); - }); - - it('should notify after timeout period', (done) => { - let queue = new ExpiringQueue(() => { - let elements = queue.popAll(); - expect(elements).to.be.eql([1, 2, 3, 4]); - elements = queue.popAll(); - expect(elements).to.have.lengthOf(0); - expect(Date.now()).to.be.equal(200); - done(); - }, 100); - - queue.push(1); - setTimeout(() => { - queue.push([2, 3]); - timer.tick(50); - }, 50); - setTimeout(() => { - queue.push([4]); - timer.tick(100); - }, 100); - timer.tick(50); - }); - }); - - const REQUEST = { - bidderCode: 'AppNexus', - bidderName: 'AppNexus', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - bidderRequestId: '1a6fc81528d0f6', - bids: [{ - bidder: 'AppNexus', - params: {}, - adUnitCode: 'container-1', - transactionId: 'de90df62-7fd0-4fbc-8787-92d133a7dc06', - sizes: [ - [300, 250] - ], - bidId: '208750227436c1', - bidderRequestId: '1a6fc81528d0f6', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f' - }], - auctionStart: 1509369418387, - timeout: 3000, - start: 1509369418389 - }; - - const RESPONSE = { - bidderCode: 'AppNexus', - width: 300, - height: 250, - statusMessage: 'Bid available', - adId: '208750227436c1', - mediaType: 'banner', - cpm: 0.015, - ad: '', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - responseTimestamp: 1509369418832, - requestTimestamp: 1509369418389, - bidder: 'AppNexus', - adUnitCode: 'container-1', - timeToRespond: 443, - size: '300x250' - }; - - const bidTimeoutArgsV1 = [{ - bidId: '2baa51527bd015', - bidderCode: 'AppNexus', - adUnitCode: 'container-1', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - }, - { - bidId: '6fe3b4c2c23092', - bidderCode: 'AppNexus', - adUnitCode: 'container-2', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f' - }]; - - describe('Analytics adapter', function() { - let ajaxStub; - let timer; - - before(function() { - ajaxStub = sandbox.stub(analyticsAdapter, 'ajaxCall'); - timer = sandbox.useFakeTimers(0); - }); - - beforeEach(function() { - sandbox.stub(events, 'getEvents').callsFake(() => { - return [] - }); - }); - - afterEach(function() { - events.getEvents.restore(); - }); - - it('should be configurable', function() { - adapterManager.registerAnalyticsAdapter({ - code: 'staq', - adapter: analyticsAdapter - }); - - adapterManager.enableAnalytics({ - provider: 'staq', - options: { - connId: 777, - queueTimeout: 1000, - url: 'https://localhost/prebid' - } - }); - - expect(analyticsAdapter.context).to.have.property('connectionId', 777); - }); - - it('should handle auction init event', function() { - events.emit(EVENTS.AUCTION_INIT, { config: {}, timeout: 3000 }); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(1); - expect(ev[0]).to.be.eql({ event: 'auctionInit', auctionId: undefined }); - }); - - it('should handle bid request event', function() { - events.emit(EVENTS.BID_REQUESTED, REQUEST); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(2); - expect(ev[1]).to.be.eql({ - adUnitCode: 'container-1', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - event: 'bidRequested', - adapter: 'AppNexus', - bidderName: 'AppNexus' - }); - }); - - it('should handle bid response event', function() { - events.emit(EVENTS.BID_RESPONSE, RESPONSE); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(3); - expect(ev[2]).to.be.eql({ - adId: '208750227436c1', - event: 'bidResponse', - adapter: 'AppNexus', - bidderName: 'AppNexus', - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - adUnitCode: 'container-1', - cpm: 0.015, - timeToRespond: 0.443, - height: 250, - width: 300, - bidWon: false, - }); - }); - - it('should handle timeouts properly', function() { - events.emit(EVENTS.BID_TIMEOUT, bidTimeoutArgsV1); - - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(5); // remember, we added 2 timeout events - expect(ev[3]).to.be.eql({ - adapter: 'AppNexus', - auctionId: '66529d4c-8998-47c2-ab3e-5b953490b98f', - bidderName: 'AppNexus', - event: 'adapterTimedOut' - }) - }); - - it('should handle winning bid', function() { - events.emit(EVENTS.BID_WON, RESPONSE); - const ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(6); - expect(ev[5]).to.be.eql({ - auctionId: '5018eb39-f900-4370-b71e-3bb5b48d324f', - adId: '208750227436c1', - event: 'bidWon', - adapter: 'AppNexus', - bidderName: 'AppNexus', - adUnitCode: 'container-1', - cpm: 0.015, - height: 250, - width: 300, - bidWon: true, - }); - }); - - it('should handle auction end event', function() { - timer.tick(447); - events.emit(EVENTS.AUCTION_END, RESPONSE); - let ev = analyticsAdapter.context.queue.peekAll(); - expect(ev).to.have.length(0); - expect(ajaxStub.calledOnce).to.be.equal(true); - let firstCallArgs0 = ajaxStub.firstCall.args[0]; - ev = JSON.parse(firstCallArgs0); - const ev6 = ev['events'][6]; - expect(ev['connId']).to.be.eql(777); - expect(ev6.auctionId).to.be.eql('5018eb39-f900-4370-b71e-3bb5b48d324f'); - expect(ev6.event).to.be.eql('auctionEnd'); - }); - }); -}); diff --git a/test/spec/modules/stnBidAdapter_spec.js b/test/spec/modules/stnBidAdapter_spec.js index 95cab32e41d..de851158ed0 100644 --- a/test/spec/modules/stnBidAdapter_spec.js +++ b/test/spec/modules/stnBidAdapter_spec.js @@ -440,8 +440,10 @@ describe('stnAdapter', function () { width: 640, height: 480, requestId: '21e12606d47ba7', + creativeId: 'creative-id-1', adomain: ['abc.com'], - mediaType: VIDEO + mediaType: VIDEO, + nurl: 'http://example.com/win/1234', }, { cpm: 12.5, @@ -449,8 +451,10 @@ describe('stnAdapter', function () { width: 300, height: 250, requestId: '21e12606d47ba7', + creativeId: 'creative-id-2', adomain: ['abc.com'], - mediaType: BANNER + mediaType: BANNER, + nurl: 'http://example.com/win/1234', }] }; @@ -461,7 +465,7 @@ describe('stnAdapter', function () { width: 640, height: 480, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-1', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: VIDEO, @@ -476,10 +480,10 @@ describe('stnAdapter', function () { requestId: '21e12606d47ba7', cpm: 12.5, currency: 'USD', - width: 640, - height: 480, + width: 300, + height: 250, ttl: TTL, - creativeId: '21e12606d47ba7', + creativeId: 'creative-id-2', netRevenue: true, nurl: 'http://example.com/win/1234', mediaType: BANNER, @@ -492,8 +496,8 @@ describe('stnAdapter', function () { it('should get correct bid response', function () { const result = spec.interpretResponse({ body: response }); - expect(Object.keys(result[0])).to.deep.equal(Object.keys(expectedVideoResponse)); - expect(Object.keys(result[1])).to.deep.equal(Object.keys(expectedBannerResponse)); + expect(result[0]).to.deep.equal(expectedVideoResponse); + expect(result[1]).to.deep.equal(expectedBannerResponse); }); it('video type should have vastXml key', function () { diff --git a/test/spec/modules/stroeerCoreBidAdapter_spec.js b/test/spec/modules/stroeerCoreBidAdapter_spec.js index 6f4874cef75..d186b0d5cd0 100644 --- a/test/spec/modules/stroeerCoreBidAdapter_spec.js +++ b/test/spec/modules/stroeerCoreBidAdapter_spec.js @@ -169,16 +169,17 @@ describe('stroeerCore bid adapter', function () { } function setupSingleWindow(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { - const win = createWindow('http://www.xyz.com/', { - parent: win, top: win, frameElement: createElement(undefined, 304), placementElements: placementElements + let singleWin = null + singleWin = createWindow('http://www.xyz.com/', { + parent: singleWin, top: singleWin, frameElement: createElement(undefined, 304), placementElements: placementElements }); - win.innerHeight = 200; + singleWin.innerHeight = 200; - sandBox.stub(utils, 'getWindowSelf').returns(win); - sandBox.stub(utils, 'getWindowTop').returns(win); + sandBox.stub(utils, 'getWindowSelf').returns(singleWin); + sandBox.stub(utils, 'getWindowTop').returns(singleWin); - return win; + return singleWin; } function setupNestedWindows(sandBox, placementElements = [createElement('div-1', 17), createElement('div-2', 54)]) { @@ -407,7 +408,6 @@ describe('stroeerCore bid adapter', function () { 'timeout': expectedTimeout, 'ref': 'https://www.example.com/?search=monkey', 'mpa': true, - 'ssl': false, 'url': 'https://www.example.com/monkey/index.html', 'bids': [{ 'sid': 'NDA=', @@ -533,6 +533,7 @@ describe('stroeerCore bid adapter', function () { 'siz': [[300, 600], [160, 60]], 'fp': undefined }, + 'sfp': undefined, }, { 'sid': 'ABC=', @@ -541,7 +542,8 @@ describe('stroeerCore bid adapter', function () { 'siz': [[100, 200], [300, 500]], 'fp': undefined }, - 'viz': undefined + 'viz': undefined, + 'sfp': undefined, } ]; @@ -555,7 +557,8 @@ describe('stroeerCore bid adapter', function () { 'siz': [640, 480], 'mim': ['video/mp4', 'video/quicktime'], 'fp': undefined - } + }, + 'sfp': undefined, } ]; @@ -597,7 +600,8 @@ describe('stroeerCore bid adapter', function () { 'ban': { 'siz': [[100, 200], [300, 500]], 'fp': undefined - } + }, + 'sfp': undefined, } ]; @@ -611,14 +615,14 @@ describe('stroeerCore bid adapter', function () { 'siz': [640, 480], 'mim': ['video/mp4', 'video/quicktime'], 'fp': undefined - } + }, + 'sfp': undefined, } ]; assert.deepEqual(serverRequestInfo.data.bids, [...expectedBannerBids, ...expectedVideoBids]); }); }); - describe('optional fields', () => { it('should skip viz field when unable to determine visibility of placement', () => { placementElements.length = 0; @@ -898,6 +902,61 @@ describe('stroeerCore bid adapter', function () { assert.deepEqual(sentOrtb2, ortb2); }); + + it('should add the special format parameters', () => { + const bidReq = buildBidderRequest(); + + const sfp0 = { + 'field1': { + 'abc': '123', + } + }; + + const sfp1 = { + 'field3': 'xyz' + }; + + bidReq.bids[0].params.sfp = utils.deepClone(sfp0); + bidReq.bids[1].params.sfp = utils.deepClone(sfp1); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.deepEqual(serverRequestInfo.data.bids[0].sfp, sfp0); + assert.deepEqual(serverRequestInfo.data.bids[1].sfp, sfp1); + }); + + it('should add the special format parameters even when it is an empty object', () => { + const bidReq = buildBidderRequest(); + + bidReq.bids[0].params.sfp = {}; + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + assert.deepEqual(serverRequestInfo.data.bids[0].sfp, {}); + assert.isUndefined(serverRequestInfo.data.bids[1].sfp); + }); + + it('should add the ortb2 site extension', () => { + const bidReq = buildBidderRequest(); + + const ortb2 = { + site: { + domain: 'example.com', + ext: { + data: { + abc: '123' + } + } + } + }; + + bidReq.ortb2 = utils.deepClone(ortb2); + + const serverRequestInfo = spec.buildRequests(bidReq.bids, bidReq); + + const sentOrtb2 = serverRequestInfo.data.ortb2; + assert.deepEqual(sentOrtb2, {site: {ext: ortb2.site.ext}}) + }); }); }); }); @@ -963,6 +1022,16 @@ describe('stroeerCore bid adapter', function () { assert.deepPropertyVal(result[0].meta, 'dsa', dsaResponse); assert.propertyVal(result[1].meta, 'dsa', undefined); }); + + it('should add campaignType to meta object', () => { + const response = buildBidderResponse(); + response.bids[1] = Object.assign(response.bids[1], {campaignType: 'RTB'}); + + const result = spec.interpretResponse({body: response}); + + assert.propertyVal(result[0].meta, 'campaignType', undefined); + assert.propertyVal(result[1].meta, 'campaignType', 'RTB'); + }); }); describe('get user syncs entry point', () => { diff --git a/test/spec/modules/stvBidAdapter_spec.js b/test/spec/modules/stvBidAdapter_spec.js index 3ef865ed2f1..9dc311562ba 100644 --- a/test/spec/modules/stvBidAdapter_spec.js +++ b/test/spec/modules/stvBidAdapter_spec.js @@ -30,12 +30,12 @@ describe('stvAdapter', function() { }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'someIncorrectParam': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -277,7 +277,7 @@ describe('stvAdapter', function() { 'width': '300', 'height': '250', 'type': 'sspHTML', - 'tag': '', + 'adTag': '', 'requestId': '220ed41385952a', 'currency': 'EUR', 'ttl': 60, @@ -338,7 +338,7 @@ describe('stvAdapter', function() { } }]; let result = spec.interpretResponse(serverResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[0])); + expect(Object.keys(result[0])).to.include.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]); }); @@ -358,7 +358,7 @@ describe('stvAdapter', function() { } }]; let result = spec.interpretResponse(serverVideoResponse, bidRequest[0]); - expect(Object.keys(result[0])).to.have.members(Object.keys(expectedResponse[1])); + expect(Object.keys(result[0])).to.include.members(Object.keys(expectedResponse[1])); expect(result[0].meta.advertiserDomains.length).to.equal(0); }); diff --git a/test/spec/modules/symitriAnalyticsAdapter_spec.js b/test/spec/modules/symitriAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..c02d5b55696 --- /dev/null +++ b/test/spec/modules/symitriAnalyticsAdapter_spec.js @@ -0,0 +1,90 @@ +import symitriAnalyticsAdapter from 'modules/symitriAnalyticsAdapter.js'; +import { expect } from 'chai'; +import adapterManager from 'src/adapterManager.js'; +import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from 'src/constants.js'; + +let events = require('src/events'); + +describe('symitri analytics adapter', function () { + beforeEach(function () { + sinon.stub(events, 'getEvents').returns([]); + }); + + afterEach(function () { + events.getEvents.restore(); + }); + + describe('track', function () { + let initOptionsValid = { + apiAuthToken: 'TOKEN1234' + }; + let initOptionsInValid = { + }; + + let bidWon = { + 'bidderCode': 'appnexus', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '393976d8770041', + 'requestId': '263efc09896d0c', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 0.5, + 'creativeId': 96846035, + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 300, + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'originalCpm': 0.5, + 'originalCurrency': 'USD', + 'auctionId': 'db377024-d866-4a24-98ac-5e430f881313', + 'responseTimestamp': 1576823894050, + 'requestTimestamp': 1576823893838, + 'bidder': 'appnexus', + 'timeToRespond': 212, + 'status': 'rendered' + }; + + adapterManager.registerAnalyticsAdapter({ + code: 'symitri', + adapter: symitriAnalyticsAdapter + }); + + afterEach(function () { + symitriAnalyticsAdapter.disableAnalytics(); + }); + + it('Test with valid apiAuthToken', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + }); + + it('Test with missing apiAuthToken', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsInValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(0); + }); + + it('Test correct winning bid is sent to server', function () { + adapterManager.enableAnalytics({ + provider: 'symitri', + options: initOptionsValid + }); + events.emit(EVENTS.BID_WON, bidWon); + expect(server.requests.length).to.equal(1); + let winEventData = JSON.parse(server.requests[0].requestBody); + expect(winEventData).to.deep.equal(bidWon); + let authToken = server.requests[0].requestHeaders['Authorization']; + expect(authToken).to.equal(initOptionsValid.apiAuthToken); + }); + }); +}); diff --git a/test/spec/modules/symitriDapRtdProvider_spec.js b/test/spec/modules/symitriDapRtdProvider_spec.js index ac19099b3c2..ec3ba4fdbed 100644 --- a/test/spec/modules/symitriDapRtdProvider_spec.js +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -1,169 +1,142 @@ -import { config } from 'src/config.js'; +import {config} from 'src/config.js'; import { dapUtils, generateRealTimeData, symitriDapRtdSubmodule, onBidWonListener, - storage, - DAP_MAX_RETRY_TOKENIZE, - DAP_SS_ID, - DAP_TOKEN, - DAP_MEMBERSHIP, - DAP_ENCRYPTED_MEMBERSHIP, + storage, DAP_MAX_RETRY_TOKENIZE, DAP_SS_ID, DAP_TOKEN, DAP_MEMBERSHIP, DAP_ENCRYPTED_MEMBERSHIP } from 'modules/symitriDapRtdProvider.js'; -import { server } from 'test/mocks/xhr.js'; -import { hook } from '../../../src/hook.js'; +import {server} from 'test/mocks/xhr.js'; +import {hook} from '../../../src/hook.js'; import { EVENTS } from 'src/constants.js'; -const responseHeader = { 'Content-Type': 'application/json' }; +const responseHeader = {'Content-Type': 'application/json'}; let events = require('src/events'); -describe('symitriDapRtdProvider', function () { +describe('symitriDapRtdProvider', function() { const testReqBidsConfigObj = { adUnits: [ { - bids: ['bid1', 'bid2'], - }, - ], + bids: ['bid1', 'bid2'] + } + ] }; - const onDone = function () { - return true; - }; + const onDone = function() { return true }; const sampleGdprConsentConfig = { - gdpr: { - consentString: null, - vendorData: {}, - gdprApplies: true, - }, + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } }; const sampleUspConsentConfig = { - usp: '1YYY', + 'usp': '1YYY' }; const sampleIdentity = { - type: 'dap-signature:1.0.0', + type: 'dap-signature:1.0.0' }; const cmoduleConfig = { - name: 'symitriDap', - waitForIt: true, - params: { - apiHostname: 'prebid.dap.akadns.net', - apiVersion: 'x1', - apiAuthToken: 'Token 1234', - domain: 'prebid.org', - identityType: 'dap-signature:1.0.0', - segtax: 503, - }, - }; + 'name': 'symitriDap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'apiAuthToken': 'Token 1234', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 708 + } + } const emoduleConfig = { - name: 'symitriDap', - waitForIt: true, - params: { - apiHostname: 'prebid.dap.akadns.net', - apiVersion: 'x1', - domain: 'prebid.org', - identityType: 'dap-signature:1.0.0', - segtax: 504, - pixelUrl: 'https://www.test.com/pixel', - }, - }; + 'name': 'symitriDap', + 'waitForIt': true, + 'params': { + 'apiHostname': 'prebid.dap.akadns.net', + 'apiVersion': 'x1', + 'domain': 'prebid.org', + 'identityType': 'dap-signature:1.0.0', + 'segtax': 710, + 'pixelUrl': 'https://www.test.com/pixel' + } + } const sampleConfig = { - api_hostname: 'prebid.dap.akadns.net', - api_version: 'x1', - domain: 'prebid.org', - segtax: 503, - identity: sampleIdentity, - }; + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 708, + 'identity': sampleIdentity + } const esampleConfig = { - api_hostname: 'prebid.dap.akadns.net', - api_version: 'x1', - domain: 'prebid.org', - segtax: 504, - identity: sampleIdentity, - }; + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 710, + 'identity': sampleIdentity + } let cacheExpiry = Math.round(Date.now() / 1000.0) + 300; // in seconds - const sampleCachedToken = { - expires_at: cacheExpiry, - token: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM', - }; - const cachedEncryptedMembership = { - expires_at: cacheExpiry, - encryptedSegments: - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI', - }; - const cachedMembership = { - expires_at: cacheExpiry, - said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', - cohorts: ['9', '11', '13'], - }; - const cachedMembershipWithDeals = { - expires_at: cacheExpiry, - said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', - cohorts: ['9', '11', '13'], - deals: [ - '{"id":"DEMODEAL555","bidfloor":5.0,"at":1,"guar":0}', - '{"id":"DEMODEAL111","bidfloor":5.0,"at":1,"guar":0}', - '{"id":"DEMODEAL123","bidfloor":5.0,"at":1,"guar":0}', - ], - }; + const sampleCachedToken = {'expires_at': cacheExpiry, 'token': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'}; + const cachedEncryptedMembership = {'expires_at': cacheExpiry, 'encryptedSegments': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..IvnIUQDqWBVYIS0gbcE9bw.Z4NZGvtogWaWlGH4e-GdYKe_PUc15M2x3Bj85rMWsN1A17mIxQIMOfg2hsQ2tgieLu5LggWPmsFu1Wbph6P0k3kOu1dVReoIhOHzxw50rP0DLHKaEZ5mLMJ7Lcosvwh4miIfFuCHlsX7J0sFgOTAp0zGo1S_UsHLtev1JflhjoSB0AoX95ALbAnyctirPuLJM8gZ1vXTiZ01jpvucGyR1lM4cWjPOeD8jPtgwaPGgSRZXE-3X2Cqy7z4Giam5Uqu74LPWTBuKtUQTGyAXA5QJoP7xwTbsU4O1f69lu3fWNqC92GijeTH1A4Zd_C-WXxWuQlDEURjlkWQoaqTHka2OqlnwukEQIf_v0r5KQQX64CTLhEUH91jeD0-E9ClcIP7pwOLxxqiKoaBmx8Mrnm_6Agj5DtTA1rusy3AL63sI_rsUxrmLrVt0Wft4aCfRkW8QpQxu8clFdOmce0NNCGeBCyCPVw9d9izrILlXJ6rItU2cpFrcbz8uw2otamF5eOFCOY3IzHedWVNNuKHFIUVC_xYSlsYvQ8f2QIP1eiMbmukcuPzmTzjw1h1_7IKaj-jJkXrnrY-TdDgX_4-_Z3rmbpXK2yTR7dBrsg-ubqFbgbKic1b4zlQEO_LbBlgPl3DYdWEuJ8CY2NUt1GfpATQGsufS2FTY1YGw_gkPe3q04l_cgLafDoxHvHh_t_0ZgPjciW82gThB_kN4RP7Mc3krVcXl_P6N1VbV07xyx0hCyVsrrxbLslI8q9wYDiLGci7mNmByM5j7SXV9jPwwPkHtn0HfMJlw2PFbIDPjgG3h7sOyLcBIJTTvuUIgpHPIkRWLIl_4FlIucXbJ7orW2nt5BWleBVHgumzGcnl9ZNcZb3W-dsdYPSOmuj0CY28MRTP2oJ1rzLInbDDpIRffJBtR7SS4nYyy7Vi09PtBigod5YNz1Q0WDSJxr8zeH_aKFaXInw7Bfo_U0IAcLiRgcT0ogsMLeQRjRFy27mr4XNJv3NtHhbdjDAwF2aClCktXyXbQaVdsPH2W71v6m2Q9rB5GQWOktw2s5f-4N1-_EBPGq6TgjF-aJZP22MJVwp1pimT50DfOzoeEqDwi862NNwNNoHmcObH0ZfwAXlhRxsgupNBe20-MNNABj2Phlfv4DUrtQbMdfCnNiypzNCmoTb7G7c_o5_JUwoV_GVkwUtvmi_IUm05P4GeMASSUw8zDKVRAj9h31C2cabM8RjMHGhkbCWpUP2pcz9zlJ7Y76Dh3RLnctfTw7DG9U4w4UlaxNZOgLUiSrGwfyapuSiuGUpuOJkBBLiHmEqAGI5C8oJpcVRccNlHxJAYowgXyFopD5Fr-FkXmv8KMkS0h5C9F6KihmDt5sqDD0qnjM0hHJgq01l7wjVnhEmPpyD-6auFQ-xDnbh1uBOJ_0gCVbRad--FSa5p-dXenggegRxOvZXJ0iAtM6Fal5Og-RCjexIHa9WhVbXhQBJpkSTWwAajZJ64eQ.yih49XB51wE-Xob7COT9OYqBrzBmIMVCQbLFx2UdzkI'}; + const cachedMembership = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13']}; + const cachedMembershipWithDeals = {'expires_at': cacheExpiry, 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..QwvU5h0NVJYaJbs5EqWCKA.XNaJHSlnsH8P-yBIr3gIEqavLONWDIFyj7QCHFwJVkwXH_EYkxrk0_26b0uMPzfJp5URnqxKZusMH9DzEJsmj8EMrKQv1y3IYYMsW5_0BdP5bcAWfG6fzOqtMOwLiYRkYiQOqn1ZVGzhovheHWEmNr2_oCY0LvAr3iN1eG_K-l-bBKvBWnwvuuGKquUfCqO8NMMq6wtkecEXM9blqFRZ7oNYmW2aIG7qcHUsrUW7HMr9Ev2Ik0sIeEUsOYrgf_X_VA64RgKSTRugS9FupMv1p54JkHokwduF9pOFmW8QLQi8itFogKGbbgvOTNnmahxQUX5FcrjjYLqHwKqC8htLdlHnO5LWU9l4A7vLXrRurvoSnh0cAJy0GsdoyEwTqR9bwVFHoPquxlJjQ4buEd7PIxpBj9Qg9oOPH3b2upbMTu5CQ9oj526eXPhP5G54nwGklm2AZ3Vggd7jCQJn45Jjiq0iIfsXAtpqS2BssCLBN8WhmUTnStK8m5sux6WUBdrpDESQjPj-EEHVS-DB5rA7icRUh6EzRxzen2rndvHvnwVhSG_l6cwPYuJ0HE0KBmYHOoqNpKwzoGiKFHrf4ReA06iWB3V2TEGJucGujhtQ9_18WwHCeJ1XtQiiO1eqa3tp5MwAbFXawVFl3FFOBgadrPyvGmkmUJ6FCLU2MSwHiYZmANMnJsokFX_6DwoAgO3U_QnvEHIVSvefc7ReeJ8fBDdmrH3LtuLrUpXsvLvEIMQdWQ_SXhjKIi7tOODR8CfrhUcdIjsp3PZs1DpuOcDB6YJKbGnKZTluLUJi3TyHgyi-DHXdTm-jSE5i_DYJGW-t2Gf23FoQhexv4q7gdrfsKfcRJNrZLp6Gd6jl4zHhUtY.nprKBsy9taQBk6dCPbA7BFF0CiGhQOEF_MazZ2bedqk', 'cohorts': ['9', '11', '13'], 'deals': ['{"id":"DEMODEAL555","bidfloor":5.0,"at":1,"guar":0}', '{"id":"DEMODEAL111","bidfloor":5.0,"at":1,"guar":0}', '{"id":"DEMODEAL123","bidfloor":5.0,"at":1,"guar":0}']}; const rtdUserObj = { name: 'www.dataprovider3.com', ext: { - taxonomyname: 'iab_audience_taxonomy', + taxonomyname: 'iab_audience_taxonomy' }, segment: [ { - id: '1918', + id: '1918' }, { - id: '1939', - }, - ], + id: '1939' + } + ] }; const encRtdUserObj = { name: 'www.dataprovider3.com', ext: { - segtax: 504, - taxonomyname: 'iab_audience_taxonomy', + segtax: 710, + taxonomyname: 'iab_audience_taxonomy' }, - segment: [], + segment: [] }; const cachedRtd = { rtd: { ortb2: { user: { - data: [rtdUserObj], - }, - }, - }, + data: [rtdUserObj] + } + } + } }; let membership = { said: cachedMembership.said, cohorts: cachedMembership.cohorts, - attributes: null, + attributes: null }; let encMembership = { - encryptedSegments: cachedEncryptedMembership.encryptedSegments, + encryptedSegments: cachedEncryptedMembership.encryptedSegments }; encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); const cachedEncRtd = { rtd: { ortb2: { user: { - data: [encRtdUserObj], - }, - }, - }, + data: [encRtdUserObj] + } + } + } }; before(() => { @@ -172,8 +145,8 @@ describe('symitriDapRtdProvider', function () { let ortb2, bidConfig; - beforeEach(function () { - bidConfig = { ortb2Fragments: {} }; + beforeEach(function() { + bidConfig = {ortb2Fragments: {}}; ortb2 = bidConfig.ortb2Fragments.global = {}; config.resetConfig(); storage.removeDataFromLocalStorage(DAP_TOKEN); @@ -182,128 +155,77 @@ describe('symitriDapRtdProvider', function () { storage.removeDataFromLocalStorage(DAP_SS_ID); }); - afterEach(function () {}); + afterEach(function () { + }); - describe('symitriDapRtdSubmodule', function () { + describe('symitriDapRtdSubmodule', function() { it('successfully instantiates', function () { expect(symitriDapRtdSubmodule.init()).to.equal(true); }); }); - describe('Get Real-Time Data', function () { - it('gets rtd from local storage cache', function () { - let dapGetMembershipFromLocalStorageStub = sinon - .stub(dapUtils, 'dapGetMembershipFromLocalStorage') - .returns(membership); - let dapGetRtdObjStub = sinon - .stub(dapUtils, 'dapGetRtdObj') - .returns(cachedRtd); - let dapGetEncryptedMembershipFromLocalStorageStub = sinon - .stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage') - .returns(encMembership); - let dapGetEncryptedRtdObjStub = sinon - .stub(dapUtils, 'dapGetEncryptedRtdObj') - .returns(cachedEncRtd); - let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); + describe('Get Real-Time Data', function() { + it('gets rtd from local storage cache', function() { + let dapGetMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership) + let dapGetRtdObjStub = sinon.stub(dapUtils, 'dapGetRtdObj').returns(cachedRtd) + let dapGetEncryptedMembershipFromLocalStorageStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership) + let dapGetEncryptedRtdObjStub = sinon.stub(dapUtils, 'dapGetEncryptedRtdObj').returns(cachedEncRtd) + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs') try { - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(sampleCachedToken) - ); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); expect(ortb2).to.eql({}); generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); - expect(ortb2.user.data).to.deep.include.members([ - encRtdUserObj, - ]); + expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); generateRealTimeData(bidConfig, () => {}, cmoduleConfig, {}); expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); } finally { - dapGetRtdObjStub.restore(); - dapGetMembershipFromLocalStorageStub.restore(); - dapGetEncryptedRtdObjStub.restore(); - dapGetEncryptedMembershipFromLocalStorageStub.restore(); - callDapApisStub.restore(); + dapGetRtdObjStub.restore() + dapGetMembershipFromLocalStorageStub.restore() + dapGetEncryptedRtdObjStub.restore() + dapGetEncryptedMembershipFromLocalStorageStub.restore() + callDapApisStub.restore() } }); }); - describe('calling DAP APIs', function () { - it('Calls callDapAPIs for unencrypted segments flow', function () { - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(sampleCachedToken) - ); - let dapExtractExpiryFromTokenStub = sinon - .stub(dapUtils, 'dapExtractExpiryFromToken') - .returns(cacheExpiry); + describe('calling DAP APIs', function() { + it('Calls callDapAPIs for unencrypted segments flow', function() { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) try { expect(ortb2).to.eql({}); dapUtils.callDapAPIs(bidConfig, () => {}, cmoduleConfig, {}); - let membership = { - cohorts: ['9', '11', '13'], - said: 'sample-said', - }; + let membership = {'cohorts': ['9', '11', '13'], 'said': 'sample-said'} let membershipRequest = server.requests[0]; - membershipRequest.respond( - 200, - responseHeader, - JSON.stringify(membership) - ); - let tokenWithExpiry = 'Sample-token-with-exp'; + membershipRequest.respond(200, responseHeader, JSON.stringify(membership)); + let tokenWithExpiry = 'Sample-token-with-exp' let tokenizeRequest = server.requests[1]; responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; - tokenizeRequest.respond( - 200, - responseHeader, - JSON.stringify(tokenWithExpiry) - ); - let data = dapUtils.dapGetRtdObj( - membership, - cmoduleConfig.params.segtax - ); - expect(ortb2.user.data).to.deep.include.members( - data.rtd.ortb2.user.data - ); + tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + let data = dapUtils.dapGetRtdObj(membership, cmoduleConfig.params.segtax); + expect(ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); } finally { dapExtractExpiryFromTokenStub.restore(); } }); - it('Calls callDapAPIs for encrypted segments flow', function () { - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(sampleCachedToken) - ); - let dapExtractExpiryFromTokenStub = sinon - .stub(dapUtils, 'dapExtractExpiryFromToken') - .returns(cacheExpiry); + it('Calls callDapAPIs for encrypted segments flow', function() { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + let dapExtractExpiryFromTokenStub = sinon.stub(dapUtils, 'dapExtractExpiryFromToken').returns(cacheExpiry) try { expect(ortb2).to.eql({}); dapUtils.callDapAPIs(bidConfig, () => {}, emoduleConfig, {}); let encMembership = 'Sample-enc-token'; let membershipRequest = server.requests[0]; responseHeader['Symitri-DAP-Token'] = encMembership; - membershipRequest.respond( - 200, - responseHeader, - JSON.stringify(encMembership) - ); - let tokenWithExpiry = 'Sample-token-with-exp'; + membershipRequest.respond(200, responseHeader, JSON.stringify(encMembership)); + let tokenWithExpiry = 'Sample-token-with-exp' let tokenizeRequest = server.requests[1]; responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; - tokenizeRequest.respond( - 200, - responseHeader, - JSON.stringify(tokenWithExpiry) - ); - let data = dapUtils.dapGetEncryptedRtdObj( - { encryptedSegments: encMembership }, - emoduleConfig.params.segtax - ); - expect(ortb2.user.data).to.deep.include.members( - data.rtd.ortb2.user.data - ); + tokenizeRequest.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + let data = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, emoduleConfig.params.segtax); + expect(ortb2.user.data).to.deep.include.members(data.rtd.ortb2.user.data); } finally { dapExtractExpiryFromTokenStub.restore(); } @@ -313,12 +235,11 @@ describe('symitriDapRtdProvider', function () { describe('dapTokenize', function () { it('dapTokenize error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize( - configAsync, - sampleIdentity, - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(400, responseHeader, JSON.stringify('error')); @@ -327,12 +248,11 @@ describe('symitriDapRtdProvider', function () { it('dapTokenize success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapTokenize( - configAsync, - sampleIdentity, - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify('success')); @@ -342,97 +262,55 @@ describe('symitriDapRtdProvider', function () { describe('dapTokenize and dapMembership incorrect params', function () { it('Onerror and config are null', function () { - expect( - dapUtils.dapTokenize(null, 'identity', onDone, null, null) - ).to.be.equal(undefined); - expect( - dapUtils.dapMembership(null, 'identity', onDone, null, null) - ).to.be.equal(undefined); - expect( - dapUtils.dapEncryptedMembership( - null, - 'identity', - onDone, - null, - null - ) - ).to.be.equal(undefined); + expect(dapUtils.dapTokenize(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(null, 'identity', onDone, null, null)).to.be.equal(undefined); const config = { - api_hostname: 'prebid.dap.akadns.net', - api_version: 1, - domain: '', - segtax: 503, + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 708 }; const encConfig = { - api_hostname: 'prebid.dap.akadns.net', - api_version: 1, - domain: '', - segtax: 504, + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 710 }; let identity = { - type: 'dap-signature:1.0.0', + type: 'dap-signature:1.0.0' }; - expect( - dapUtils.dapTokenize(config, identity, onDone, null, null) - ).to.be.equal(undefined); - expect( - dapUtils.dapMembership(config, 'token', onDone, null, null) - ).to.be.equal(undefined); - expect( - dapUtils.dapEncryptedMembership( - encConfig, - 'token', - onDone, - null, - null - ) - ).to.be.equal(undefined); + expect(dapUtils.dapTokenize(config, identity, onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapMembership(config, 'token', onDone, null, null)).to.be.equal(undefined); + expect(dapUtils.dapEncryptedMembership(encConfig, 'token', onDone, null, null)).to.be.equal(undefined); }); }); describe('Getting dapTokenize, dapMembership and dapEncryptedMembership from localstorage', function () { it('dapGetTokenFromLocalStorage success', function () { - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(sampleCachedToken) - ); - expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal( - sampleCachedToken.token - ); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(dapUtils.dapGetTokenFromLocalStorage(60)).to.be.equal(sampleCachedToken.token); }); it('dapGetMembershipFromLocalStorage success', function () { - storage.setDataInLocalStorage( - DAP_MEMBERSHIP, - JSON.stringify(cachedMembership) - ); - expect( - JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage()) - ).to.be.equal(JSON.stringify(membership)); + storage.setDataInLocalStorage(DAP_MEMBERSHIP, JSON.stringify(cachedMembership)); + expect(JSON.stringify(dapUtils.dapGetMembershipFromLocalStorage())).to.be.equal(JSON.stringify(membership)); }); it('dapGetEncryptedMembershipFromLocalStorage success', function () { - storage.setDataInLocalStorage( - DAP_ENCRYPTED_MEMBERSHIP, - JSON.stringify(cachedEncryptedMembership) - ); - expect( - JSON.stringify( - dapUtils.dapGetEncryptedMembershipFromLocalStorage() - ) - ).to.be.equal(JSON.stringify(encMembership)); + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)); + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.be.equal(JSON.stringify(encMembership)); }); }); describe('dapMembership', function () { it('dapMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership( - configAsync, - 'token', - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify('success')); @@ -441,12 +319,11 @@ describe('symitriDapRtdProvider', function () { it('dapMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(sampleConfig)); - let submoduleCallback = dapUtils.dapMembership( - configAsync, - 'token', - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(400, responseHeader, JSON.stringify('error')); @@ -457,12 +334,11 @@ describe('symitriDapRtdProvider', function () { describe('dapEncMembership', function () { it('dapEncMembership success callback', function () { let configAsync = JSON.parse(JSON.stringify(esampleConfig)); - let submoduleCallback = dapUtils.dapEncryptedMembership( - configAsync, - 'token', - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify('success')); @@ -471,12 +347,11 @@ describe('symitriDapRtdProvider', function () { it('dapEncMembership error callback', function () { let configAsync = JSON.parse(JSON.stringify(esampleConfig)); - let submoduleCallback = dapUtils.dapEncryptedMembership( - configAsync, - 'token', - onDone, - function (token, status, xhr, onDone) {}, - function (xhr, status, error, onDone) {} + let submoduleCallback = dapUtils.dapEncryptedMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } ); let request = server.requests[0]; request.respond(400, responseHeader, JSON.stringify('error')); @@ -489,19 +364,13 @@ describe('symitriDapRtdProvider', function () { let membership = { said: 'item.said1', cohorts: 'item.cohorts', - attributes: null, + attributes: null }; - let getDapMembershipStub = sinon - .stub(dapUtils, 'dapGetMembershipFromLocalStorage') - .returns(membership); + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); try { - generateRealTimeData( - testReqBidsConfigObj, - onDone, - cmoduleConfig - ); + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); expect(getDapMembershipStub.calledOnce).to.be.equal(true); } finally { getDapMembershipStub.restore(); @@ -516,16 +385,10 @@ describe('symitriDapRtdProvider', function () { encryptedSegments: 'enc.seg', }; - let getDapEncMembershipStub = sinon - .stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage') - .returns(encMembership); + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); try { - generateRealTimeData( - testReqBidsConfigObj, - onDone, - emoduleConfig - ); + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); } finally { getDapEncMembershipStub.restore(); @@ -540,173 +403,101 @@ describe('symitriDapRtdProvider', function () { apiHostname: 'prebid.dap.akadns.net', apiVersion: 'x1', domain: 'prebid.org', - segtax: 503, + segtax: 708 }; - expect( - dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone) - ).to.equal(undefined); - const membership = { cohorts: ['1', '5', '7'] }; - expect( - dapUtils.dapGetRtdObj(membership, config.segtax) - ).to.not.equal(undefined); + expect(dapUtils.dapRefreshMembership(ortb2, config, 'token', onDone)).to.equal(undefined) + const membership = {cohorts: ['1', '5', '7']} + expect(dapUtils.dapGetRtdObj(membership, config.segtax)).to.not.equal(undefined); }); }); describe('checkAndAddRealtimeData test', function () { - it('add realtime data for segtax 503 and 504', function () { - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); - dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 504); + it('add realtime data for segtax 708 and 710', function () { + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); + dapUtils.checkAndAddRealtimeData(ortb2, cachedEncRtd, 710); expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); - dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 708); expect(ortb2.user.data).to.deep.include.members([rtdUserObj]); }); }); describe('dapExtractExpiryFromToken test', function () { it('test dapExtractExpiryFromToken function', function () { - let tokenWithoutExpiry = - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM'; - expect( - dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry) - ).to.equal(undefined); - let tokenWithExpiry = - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw'; - expect( - dapUtils.dapExtractExpiryFromToken(tokenWithExpiry) - ).to.equal(1643830369); + let tokenWithoutExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..6buzBd2BjtgoyaNbHN8YnQ.l38avCfm3sYNy798-ETYOugz0cOx1cCkjACkAhYszxzrZ0sUJ0AiF-NdDXVTiTyp2Ih3vCWKzS0rKJ8lbS1zhyEVWVu91QwtwseM2fBbwA5ggAgBEo5wV-IXqDLPxVnxsPF0D3hP6cNCiH9Q2c-vULfsLhMhG5zvvZDPBbn4hUY5fKB8LoCBTF9rbuuWGYK1nramnb4AlS5UK82wBsHQea1Ou_Kp5wWCMNZ6TZk5qKIuRBfPIAhQblWvHECaHXkg1wyoM9VASs_yNhne7RR-qkwzbFiPFiMJibNOt9hF3_vPDJO5-06ZBjRTP1BllYGWxI-uQX6InzN18Wtun2WHqg.63sH0SNlIRcsK57v0pMujfB_nhU8Y5CuQbsHqH5MGoM' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithoutExpiry)).to.equal(undefined); + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' + expect(dapUtils.dapExtractExpiryFromToken(tokenWithExpiry)).to.equal(1643830369); }); }); describe('dapRefreshToken test', function () { it('test dapRefreshToken success response', function () { - dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone); + dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone) let request = server.requests[0]; responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; - request.respond( - 200, - responseHeader, - JSON.stringify(sampleCachedToken.token) - ); - expect( - JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token - ).to.be.equal(sampleCachedToken.token); + request.respond(200, responseHeader, JSON.stringify(sampleCachedToken.token)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).token).to.be.equal(sampleCachedToken.token); }); it('test dapRefreshToken success response with deviceid 100', function () { - dapUtils.dapRefreshToken(ortb2, esampleConfig, true, onDone); + dapUtils.dapRefreshToken(ortb2, esampleConfig, true, onDone) let request = server.requests[0]; responseHeader['Symitri-DAP-100'] = sampleCachedToken.token; request.respond(200, responseHeader, ''); - expect( - storage.getDataFromLocalStorage('dap_deviceId100') - ).to.be.equal(sampleCachedToken.token); + expect(storage.getDataFromLocalStorage('dap_deviceId100')).to.be.equal(sampleCachedToken.token); }); it('test dapRefreshToken success response with exp claim', function () { - dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone); + dapUtils.dapRefreshToken(ortb2, sampleConfig, true, onDone) let request = server.requests[0]; - let tokenWithExpiry = - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw'; + let tokenWithExpiry = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQzODMwMzY5fQ..hTbcSQgmmO0HUJJrQ5fRHw.7zjrQXNNVkb-GD0ZhIVhEPcWbyaDBilHTWv-bp1lFZ9mdkSC0QbcAvUbYteiTD7ya23GUwcL2WOW8WgRSHaWHOJe0B5NDqfdUGTzElWfu7fFodRxRgGmwG8Rq5xxteFKLLGHLf1mFYRJKDtjtgajGNUKIDfn9AEt-c5Qz4KU8VolG_KzrLROx-f6Z7MnoPTcwRCj0WjXD6j2D6RAZ80-mKTNIsMIELdj6xiabHcjDJ1WzwtwCZSE2y2nMs451pSYp8W-bFPfZmDDwrkjN4s9ASLlIXcXgxK-H0GsiEbckQOZ49zsIKyFtasBvZW8339rrXi1js-aBh99M7aS5w9DmXPpUDmppSPpwkeTfKiqF0cQiAUq8tpeEQrGDJuw3Qt2.XI8h9Xw-VZj_NOmKtV19wLM63S4snos7rzkoHf9FXCw' responseHeader['Symitri-DAP-Token'] = tokenWithExpiry; - request.respond( - 200, - responseHeader, - JSON.stringify(tokenWithExpiry) - ); - expect( - JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)) - .expires_at - ).to.be.equal(1643830359); + request.respond(200, responseHeader, JSON.stringify(tokenWithExpiry)); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(1643830359); }); it('test dapRefreshToken error response', function () { - storage.setDataInLocalStorage( - DAP_TOKEN, - JSON.stringify(sampleCachedToken) - ); - dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone); + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); - expect( - JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)) - .expires_at - ).to.be.equal(cacheExpiry); // Since the expiry is same, the token is not updated in the cache + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)).expires_at).to.be.equal(cacheExpiry);// Since the expiry is same, the token is not updated in the cache }); }); describe('dapRefreshEncryptedMembership test', function () { it('test dapRefreshEncryptedMembership success response', function () { let expiry = Math.round(Date.now() / 1000.0) + 3600; // in seconds - let encMembership = - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; - dapUtils.dapRefreshEncryptedMembership( - ortb2, - esampleConfig, - sampleCachedToken.token, - onDone - ); + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQifQ..f8_At4OqeQXyQcSwThOJ_w.69ImVQ3bEZ6QP7ROCRpAJjNcKY49SEPYR6qTp_8l7L8kQdPbpi4wmuOzt78j7iBrX64k2wltzmQFjDmVKSxDhrEguxpgx6t-L1tT8ZA0UosMWpVsgmKEZxOn2e9ES3jw8RNCS4WSWocSPQX33xSb51evXjm9E1s0tGoLnwXl0GsUvzRsSU86wQG6RZnAQTi7s-r-M2TKibdDjUqgIt62vJ-aBZ7RWw91MINgOdmDNs1bFfbBX5Cy1kd4-kjvRDz_aJ6zHX4sK_7EmQhGEY3tW-A3_l2I88mw-RSJaPkb_IWg0QpVwXDaE2F2g8NpY1PzCRvG_NIE8r28eK5q44OMVitykHmKmBXGDj7z2JVgoXkfo5u0I-dypZARn4GP_7niK932avB-9JD7Mz3TrlU4GZ7IpYfJ91PMsRhrs5xNPQwLZbpuhF76A7Dp7iss71UjkGCiPTU6udfRb4foyf_7xEF66m1eQVcVaMdxEbMuu9GBfdr-d04TbtJhPfUV8JfxTenvRYoi13n0j5kH0M5OgaSQD9kQ3Mrd9u-Cms-BGtT0vf-N8AaFZY_wn0Y4rkpv5HEaH7z3iT4RCHINWrXb_D0WtjLTKQi2YmF8zMlzUOewNJGwZRwbRwxc7JoDIKEc5RZkJYevfJXOEEOPGXZ7AGZxOEsJawPqFqd_nOUosCZS4akHhcDPcVowoecVAV0hhhoS6JEY66PhPp1snbt6yqA-fQhch7z8Y-DZT3Scibvffww3Scg_KFANWp0KeEvHG0vyv9R2F4o66viSS8y21MDnM7Yjk8C-j7aNMldUQbjN_7Yq1nkfe0jiBX_hsINBRPgJHUY4zCaXuyXs-JZZfU92nwG0RT3A_3RP2rpY8-fXp9d3C2QJjEpnmHvTMsuAZCQSBe5DVrJwN_UKedxcJEoOt0wLz6MaCMyYZPd8tnQeqYK1cd3RgQDXtzKC0HDw1En489DqJXEst4eSSkaaW1lImLeaF8XCOaIqPqoyGk4_6KVLw5Q7OnpczuXqYKMd9UTMovGeuTuo1k0ddfEqTq9QwxkwZL51AiDRnwTCAeYBU1krV8FCJQx-mH_WPB5ftZj-o_3pbvANeRk27QBVmjcS-tgDllJkWBxX-4axRXzLw8pUUUZUT_NOL0OiqUCWVm0qMBEpgRQ57Se42-hkLMTzLhhGJOnVcaXU1j4ep-N7faNvbgREBjf_LgzvaWS90a2NJ9bB_J9FyXelhCN_AMLfdOS3fHkeWlZ0u0PMbn5DxXRMe0l9jB-2VJZhcPQRlWoYyoCO3l4F5ZmuQP5Xh9CU4tvSWih6jlwMDgdVWuTpdfPD5bx8ccog3JDq87enx-QtPzLU3gMgouNARJGgNwKS_GJSE1uPrt2oiqgZ3Z0u_I5MKvPdQPV3o-4rsaE730eB4OwAOF-mkGWpzy8Pbl-Qe5PR9mHBhuyJgZ-WDSCHl5yvet2kfO9mPXZlqBQ26fzTcUYH94MULAZn36og6w.3iKGv-Le-AvRmi26W1v6ibRLGbwKbCR92vs-a9t55hw'; + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; responseHeader['Symitri-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj( - { encryptedSegments: encMembership }, - 504 - ); - expect(ortb2.user.data).to.deep.include.members( - rtdObj.rtd.ortb2.user.data - ); - expect( - JSON.parse( - storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP) - ).expires_at - ).to.equal(expiry); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(expiry); }); it('test dapRefreshEncryptedMembership success response with exp claim', function () { - let encMembership = - 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; - dapUtils.dapRefreshEncryptedMembership( - ortb2, - esampleConfig, - sampleCachedToken.token, - onDone - ); + let encMembership = 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoic29tZXNlY3JldGludmF1bHQiLCJleHAiOjE2NDM4MzA2NDB9..inYoxwht_aqTIWqGhEm_Gw.wDcCUOCwtqgnNUouaD723gKfm7X7bgkHgtiX4mr07P3tWk25PUQunmwTLhWBB5CYzzGIfIvveG_u4glNRLi_eRSQV4ihKKk1AN-BSSJ3d0CLAdY9I1WG5vX1VmopXyKnV90bl9SLNqnhg4Vxe6YU4ogTYxsKHuIN1EeIH4hpl-HbCQWQ1DQt4mB-MQF8V9AWTfU0D7sFMSK8f9qj6NGmf1__oHdHUlws0t5V2UAn_dhJexsuREK_gh65pczCuly5eEcziZ82LeP-nOhKWSRHB_tS_mKXrRU6_At_EVDgtfA3PSBJ6eQylCii6bTL42vZzz4jZhJv_3eLfRdKqpVT5CWNBzcDoQ2VcQgKgIBtPJ45KFfAYTQ6kdl21QMSjqtu8GTsv1lEZtrqHY6zRiG8_Mu28-PmjEw4LDdZmBDOeroue_MJD6wuE_jlE7J2iVdo8CkVnoRgzFwNbKBo7CK4z0WahV9rhuOm0LKAN5H0jF_gj696U-3fVTDTIb8ndNKNI2_xAhvWs00BFGtUtWgr8QGDGRTDCNGsDgnb_Vva9xCqVOyAE9O3Fq1QYl-tMA-KkBt3zzvmFFpOxpOyH-lUubKLKlsrxKc3GSyVEQ9DDLhrXXJgR5H5BSE4tjlK7p3ODF5qz0FHtIj7oDcgLazFO7z2MuFy2LjJmd3hKl6ujcfYEDiQ4D3pMIo7oiU33aFBD1YpzI4-WzNfJlUt1FoK0-DAXpbbV95s8p08GOD4q81rPw5hRADKJEr0QzrbDwplTWCzT2fKXMg_dIIc5AGqGKnVRUS6UyF1DnHpudNIJWxyWZjWIEw_QNjU0cDFmyPSyKxNrnfq9w8WE2bfbS5KTicxei5QHnC-cnL7Nh7IXp7WOW6R1YHbNPT7Ad4OhnlV-jjrXwkSv4wMAbfwAWoSCchGh7uvENNAeJymuponlJbOgw_GcYM73hMs8Z8W9qxRfbyF4WX5fDKXg61mMlaieHkc0EnoC5q7uKyXuZUehHZ76JLDFmewslLkQq5SkVCttzJePBnY1ouPEHw5ZTzUnG5f01QQOVcjIN-AqXNDbG5IOwq0heyS6vVfq7lZKJdLDVQ21qRjazGPaqYwLzugkWkzCOzPTgyFdbXzgjfmJwylHSOM5Jpnul84GzxEQF-1mHP2A8wtIT-M7_iX24It2wwWvc8qLA6GEqruWCtNyoug8CXo44mKdSSCGeEZHtfMbzXdLIBHCy2jSHz5i8S7DU_R7rE_5Ssrb81CqIYbgsAQBHtOYoyvzduTOruWcci4De0QcULloqImIEHUuIe2lnYO889_LIx5p7nE3UlSvLBo0sPexavFUtHqI6jdG6ye9tdseUEoNBDXW0aWD4D-KXX1JLtAgToPVUtEaXCJI7QavwO9ZG6UZM6jbfuJ5co0fvUXp6qYrFxPQo2dYHkar0nT6s1Zg5l2g8yWlLUJrHdHAzAw_NScUp71OpM4TmNsLnYaPVPcOxMvtJXTanbNWr0VKc8gy9q3k_1XxAnQwiduNs7f5bA-6qCVpayHv5dE7mUhFEwyh1_w95jEaURsQF_hnnd2OqRkADfiok4ZiPU2b38kFW1LXjpI39XXES3JU0e08Rq2uuelyLbCLWuJWq_axuKSZbZvpYeqWtIAde8FjCiO7RPlEc0nyzWBst8RBxQ-Bekg9UXPhxBRcm0HwA.Q2cBSFOQAC-QKDwmjrQXnVQd3jNOppMl9oZfd2yuKeY'; + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; responseHeader['Symitri-DAP-Token'] = encMembership; request.respond(200, responseHeader, encMembership); - let rtdObj = dapUtils.dapGetEncryptedRtdObj( - { encryptedSegments: encMembership }, - 504 - ); - expect(ortb2.user.data).to.deep.include.members( - rtdObj.rtd.ortb2.user.data - ); - expect( - JSON.parse( - storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP) - ).expires_at - ).to.equal(1643830630); + let rtdObj = dapUtils.dapGetEncryptedRtdObj({'encryptedSegments': encMembership}, 710) + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)).expires_at).to.equal(1643830630); }); it('test dapRefreshEncryptedMembership error response', function () { - dapUtils.dapRefreshEncryptedMembership( - ortb2, - esampleConfig, - sampleCachedToken.token, - onDone - ); + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); expect(ortb2).to.eql({}); }); it('test dapRefreshEncryptedMembership 403 error response', function () { - dapUtils.dapRefreshEncryptedMembership( - ortb2, - esampleConfig, - sampleCachedToken.token, - onDone - ); + dapUtils.dapRefreshEncryptedMembership(ortb2, esampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(403, responseHeader, 'error'); let requestTokenize = server.requests[1]; @@ -714,74 +505,39 @@ describe('symitriDapRtdProvider', function () { requestTokenize.respond(200, responseHeader, ''); let requestMembership = server.requests[2]; requestMembership.respond(403, responseHeader, 'error'); - expect(server.requests.length).to.be.equal( - DAP_MAX_RETRY_TOKENIZE + 2 - ); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE + 2); }); }); describe('dapRefreshMembership test', function () { it('test dapRefreshMembership success response', function () { - let membership = { - cohorts: ['9', '11', '13'], - said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU', - }; - dapUtils.dapRefreshMembership( - ortb2, - sampleConfig, - sampleCachedToken.token, - onDone - ); + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIn0..17wnrhz6FbWx0Cf6LXpm1A.m9PKVCradk3CZokNKzVHzE06TOqiXYeijgxTQUiQy5Syx-yicnO8DyYX6zQ6rgPcNgUNRt4R4XE5MXuK0laUVQJr9yc9g3vUfQfw69OMYGW_vRlLMPzoNOhF2c4gSyfkRrLr7C0qgALmZO1D11sPflaCTNmO7pmZtRaCOB5buHoWcQhp1bUSJ09DNDb31dX3llimPwjNGSrUhyq_EZl4HopnnjxbM4qVNMY2G_43C_idlVOvbFoTxcDRATd-6MplJoIOIHQLDZEetpIOVcbEYN9gQ_ndBISITwuu5YEgs5C_WPHA25nm6e4BT5R-tawSA8yPyQAupqE8gk4ZWq_2-T0cqyTstIHrMQnZ_vysYN7h6bkzE-KeZRk7GMtySN87_fiu904hLD9QentGegamX6UAbVqQh7Htj7SnMHXkEenjxXAM5mRqQvNCTlw8k-9-VPXs-vTcKLYP8VFf8gMOmuYykgWac1gX-svyAg-24mo8cUbqcsj9relx4Qj5HiXUVyDMBZxK-mHZi-Xz6uv9GlggcsjE13DSszar-j2OetigpdibnJIxRZ-4ew3-vlvZ0Dul3j0LjeWURVBWYWfMjuZ193G7lwR3ohh_NzlNfwOPBK_SYurdAnLh7jJgTW-lVLjH2Dipmi9JwX9s03IQq9opexAn7hlM9oBI6x5asByH8JF8WwZ5GhzDjpDwpSmHPQNGFRSyrx_Sh2CPWNK6C1NJmLkyqAtJ5iw0_al7vPDQyZrKXaLTjBCUnbpJhUZ8dUKtWLzGPjzFXp10muoDIutd1NfyKxk1aWGhx5aerYuLdywv6cT_M8RZTi8924NGj5VA30V5OvEwLLyX93eDhntXZSCbkPHpAfiRZNGXrPY.GhCbWGQz11mIRD4uPKmoAuFXDH7hGnils54zg7N7-TU'} + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503); - expect(ortb2.user.data).to.deep.include.members( - rtdObj.rtd.ortb2.user.data - ); + let rtdObj = dapUtils.dapGetRtdObj(membership, 708); + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); }); it('test dapRefreshMembership success response with exp claim', function () { - let membership = { - cohorts: ['9', '11', '13'], - said: 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE', - }; - dapUtils.dapRefreshMembership( - ortb2, - sampleConfig, - sampleCachedToken.token, - onDone - ); + let membership = {'cohorts': ['9', '11', '13'], 'said': 'eyJhbGciOiJkaXIiLCJlbmMiOiJBMTI4Q0JDLUhTMjU2Iiwia2lkIjoicGFzc3dvcmQxIiwiZXhwIjoxNjQ3OTcxNTU4fQ..ptdM5WO-62ypXlKxFXD4FQ.waEo9MHS2NYQCi-zh_p6HgT9BdqGyQbBq4GfGLfsay4nRBgICsTS-VkV6e7xx5U1T8BgpKkRJIZBwTOY5Pkxk9FpK5nnffDSEljRrp1LXLCkNP4qwrlqHInFbZsonNWW4_mW-7aUPlTwIsTbfjTuyHdXHeQa1ALrwFFFWE7QUmPNd2RsHjDwUsxlJPEb5TnHn5W0Mgo_PQZaxvhJInMbxPgtJLoqnJvOqCBEoQY7au7ALZL_nWK8XIwPMF19J7Z3cBg9vQInhr_E3rMdQcAFHEzYfgoNcIYCCR0t1UOqUE3HNtX-E64kZAYKWdlsBb9eW5Gj9hHYyPNL_4Hntjg5eLXGpsocMg0An-qQKGC6hkrxKzeM-GrjpvSaQLNs4iqDpHUtzA02LW_vkLkMNRUiyXVJ3FUZwfyq6uHSRKWZ6UFdAfL0rfJ8q8x8Ll-qJO2Jfyvidlsi9FIs7x1WJrvDCKepfAQM1UXRTonrQljFBAk83PcL2bmWuJDgJZ0lWS4VnZbIf6A7fDourmkDxdVRptvQq5nSjtzCA6whRw0-wGz8ehNJsaJw9H_nG9k4lRKs7A5Lqsyy7TVFrAPjnA_Q1a2H6xF2ULxrtIqoNqdX7k9RjowEZSQlZgZUOAmI4wzjckdcSyC_pUlYBMcBwmlld34mmOJe9EBHAxjdci7Q_9lvj1HTcwGDcQITXnkW9Ux5Jkt9Naw-IGGrnEIADaT2guUAto8W_Gb05TmwHSd6DCmh4zepQCbqeVe6AvPILtVkTgsTTo27Q-NvS7h-XtthJy8425j5kqwxxpZFJ0l0ytc6DUyNCLJXuxi0JFU6-LoSXcROEMVrHa_Achufr9vHIELwacSAIHuwseEvg_OOu1c1WYEwZH8ynBLSjqzy8AnDj24hYgA0YanPAvDqacrYrTUFqURbHmvcQqLBTcYa_gs7uDx4a1EjtP_NvHRlvCgGAaASrjGMhTX8oJxlTqahhQ.pXm-7KqnNK8sbyyczwkVYhcjgiwkpO8LjBBVw4lcyZE'}; + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(membership)); - let rtdObj = dapUtils.dapGetRtdObj(membership, 503); - expect(ortb2.user.data).to.deep.include.members( - rtdObj.rtd.ortb2.user.data - ); - expect( - JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)) - .expires_at - ).to.be.equal(1647971548); + let rtdObj = dapUtils.dapGetRtdObj(membership, 708) + expect(ortb2.user.data).to.deep.include.members(rtdObj.rtd.ortb2.user.data); + expect(JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)).expires_at).to.be.equal(1647971548); }); it('test dapRefreshMembership 400 error response', function () { - dapUtils.dapRefreshMembership( - ortb2, - sampleConfig, - sampleCachedToken.token, - onDone - ); + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(400, responseHeader, 'error'); expect(ortb2).to.eql({}); }); it('test dapRefreshMembership 403 error response', function () { - dapUtils.dapRefreshMembership( - ortb2, - sampleConfig, - sampleCachedToken.token, - onDone - ); + dapUtils.dapRefreshMembership(ortb2, sampleConfig, sampleCachedToken.token, onDone) let request = server.requests[0]; request.respond(403, responseHeader, 'error'); expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE); @@ -790,54 +546,34 @@ describe('symitriDapRtdProvider', function () { describe('dapGetEncryptedMembershipFromLocalStorage test', function () { it('test dapGetEncryptedMembershipFromLocalStorage function with valid cache', function () { - storage.setDataInLocalStorage( - DAP_ENCRYPTED_MEMBERSHIP, - JSON.stringify(cachedEncryptedMembership) - ); - expect( - JSON.stringify( - dapUtils.dapGetEncryptedMembershipFromLocalStorage() - ) - ).to.equal(JSON.stringify(encMembership)); + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(cachedEncryptedMembership)) + expect(JSON.stringify(dapUtils.dapGetEncryptedMembershipFromLocalStorage())).to.equal(JSON.stringify(encMembership)); }); it('test dapGetEncryptedMembershipFromLocalStorage function with invalid cache', function () { let expiry = Math.round(Date.now() / 1000.0) - 100; // in seconds - let encMembership = { - expiry: expiry, - encryptedSegments: cachedEncryptedMembership.encryptedSegments, - }; - storage.setDataInLocalStorage( - DAP_ENCRYPTED_MEMBERSHIP, - JSON.stringify(encMembership) - ); - expect( - dapUtils.dapGetEncryptedMembershipFromLocalStorage() - ).to.equal(null); + let encMembership = {'expiry': expiry, 'encryptedSegments': cachedEncryptedMembership.encryptedSegments} + storage.setDataInLocalStorage(DAP_ENCRYPTED_MEMBERSHIP, JSON.stringify(encMembership)) + expect(dapUtils.dapGetEncryptedMembershipFromLocalStorage()).to.equal(null); }); }); describe('Symitri-DAP-SS-ID test', function () { it('Symitri-DAP-SS-ID present in response header', function () { let expiry = Math.round(Date.now() / 1000.0) + 300; // in seconds - dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone); + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; let sampleSSID = 'Test_SSID_Spec'; responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; responseHeader['Symitri-DAP-SS-ID'] = sampleSSID; request.respond(200, responseHeader, ''); - expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal( - JSON.stringify(sampleSSID) - ); + expect(storage.getDataFromLocalStorage(DAP_SS_ID)).to.be.equal(JSON.stringify(sampleSSID)); }); it('Test if Symitri-DAP-SS-ID is present in request header', function () { let expiry = Math.round(Date.now() / 1000.0) + 100; // in seconds - storage.setDataInLocalStorage( - DAP_SS_ID, - JSON.stringify('Test_SSID_Spec') - ); - dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone); + storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify('Test_SSID_Spec')) + dapUtils.dapRefreshToken(ortb2, sampleConfig, false, onDone) let request = server.requests[0]; let ssidHeader = request.requestHeaders['Symitri-DAP-SS-ID']; responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; @@ -848,39 +584,28 @@ describe('symitriDapRtdProvider', function () { describe('Test gdpr and usp consent handling', function () { it('Gdpr applies and gdpr consent string not present', function () { - expect( - symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig) - ).to.equal(false); + expect(symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(false); }); it('Gdpr applies and gdpr consent string is present', function () { - sampleGdprConsentConfig.gdpr.consentString = - 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; - expect( - symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig) - ).to.equal(true); + sampleGdprConsentConfig.gdpr.consentString = 'BOJ/P2HOJ/P2HABABMAAAAAZ+A=='; + expect(symitriDapRtdSubmodule.init(null, sampleGdprConsentConfig)).to.equal(true); }); it('USP consent present and user have opted out', function () { - expect( - symitriDapRtdSubmodule.init(null, sampleUspConsentConfig) - ).to.equal(false); + expect(symitriDapRtdSubmodule.init(null, sampleUspConsentConfig)).to.equal(false); }); it('USP consent present and user have not been provided with option to opt out', function () { - expect(symitriDapRtdSubmodule.init(null, { usp: '1NYY' })).to.equal( - false - ); + expect(symitriDapRtdSubmodule.init(null, {'usp': '1NYY'})).to.equal(false); }); it('USP consent present and user have not opted out', function () { - expect(symitriDapRtdSubmodule.init(null, { usp: '1YNY' })).to.equal( - true - ); + expect(symitriDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); }); }); - describe('Test identifier is added properly to apiParams', function () { + describe('Test identifier is added properly to apiParams', function() { let sandbox; beforeEach(() => { @@ -894,31 +619,27 @@ describe('symitriDapRtdProvider', function () { it('passed identifier is handled', async function () { const test_identity = 'test_identity_1234'; let identity = { - value: test_identity, + value: test_identity }; let apiParams = { - type: identity.type, + 'type': identity.type, }; if (window.crypto && window.crypto.subtle) { - let hid = await dapUtils - .addIdentifier(identity, apiParams) - .then(); - expect(hid['identity']).is.equal( - '843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7' - ); + let hid = await dapUtils.addIdentifier(identity, apiParams).then(); + expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); } else { - expect(window.crypto.subtle).is.undefined; + expect(window.crypto.subtle).is.undefined } }); it('passed undefined identifier is handled', async function () { const test_identity = undefined; let identity = { - identity: test_identity, - }; + identity: test_identity + } let apiParams = { - type: identity.type, + 'type': identity.type, }; let hid = await dapUtils.addIdentifier(identity, apiParams); @@ -927,51 +648,13 @@ describe('symitriDapRtdProvider', function () { }); describe('onBidResponseEvent', function () { - const bidResponse = { - adId: 'ad_123', - bidder: 'test_bidder', - bidderCode: 'test_bidder_code', - cpm: '1.5', - creativeId: 'creative_123', - dealId: 'DEMODEAL555', - mediaType: 'banner', - responseTimestamp: '1725892736147', - ad: '', - }; - let url = - emoduleConfig.params.pixelUrl + - '?token=' + - sampleCachedToken.token + - '&ad_id=' + - bidResponse.adId + - '&bidder=' + - bidResponse.bidder + - '&bidder_code=' + - bidResponse.bidderCode + - '&cpm=' + - bidResponse.cpm + - '&creative_id=' + - bidResponse.creativeId + - '&deal_id=' + - bidResponse.dealId + - '&media_type=' + - bidResponse.mediaType + - '&response_timestamp=' + - bidResponse.responseTimestamp; + const bidResponse = {adId: 'ad_123', bidder: 'test_bidder', bidderCode: 'test_bidder_code', cpm: '1.5', creativeId: 'creative_123', dealId: 'DEMODEAL555', mediaType: 'banner', responseTimestamp: '1725892736147', ad: ''}; + let url = emoduleConfig.params.pixelUrl + '?token=' + sampleCachedToken.token + '&ad_id=' + bidResponse.adId + '&bidder=' + bidResponse.bidder + '&bidder_code=' + bidResponse.bidderCode + '&cpm=' + bidResponse.cpm + '&creative_id=' + bidResponse.creativeId + '&deal_id=' + bidResponse.dealId + '&media_type=' + bidResponse.mediaType + '&response_timestamp=' + bidResponse.responseTimestamp; let adPixel = `${bidResponse.ad}", + 'adomain': ['google.com'], + 'iurl': 'https://cdn.tapnative.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + nativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': '{\"native\":{\"ver\":\"1.1\",\"assets\": [{\"id\":1,\"required\":1,\"title\":{\"text\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":2,\"required\":1,\"img\":{\"url\":\"https://cdn.tapnative.com/1_368852_0.png\",\"w\":500,\"h\":300,\"type\":\"3\"}},{\"id\":3,\"required\":0,\"data\":{\"value\":\"Diabetes In Control. A free weekly diabetes newsletter for Medical Professionals.\"}},{\"id\":4,\"required\":1,\"data\":{\"value\":\"Integrative Approaches: Merging Traditional and Alternative \"}},{\"id\":6,\"required\":1,\"data\":{\"value\":\"URL\"}}],\"link\":{\"url\":\"https://r.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.AdLinkManager?qs=H4sIAAAAAAAAAx2U2QHDIAxDVwKDr3F84P1HqNLPtMSRpSeG9RiPXH+5a474KzO/47YX7UoP50m61fLujlNjb76/8ZiblkimHq5nL/ZRedp3031x1tnk55LjSNN6h9/Zq+qmaLLuWTl74m1ZJKnb+m2OtQm/3L4sb933pM92qMOgjJ41MYmPXKnndRVKs+9bfSEumoZIFpTXuXbCP+WXuzl725E3O+9odi5OJrnBzhwjx9+UnFN3nTNt1/HY5aeljKtvZYpoJHNXr8BWa8ysKQY7ZmNA3DHK2qRwY7+zLu+xm9z5eheJ4Pv2usSptvO3p7JHrnXn0T5yVWdccp9Yz7hhoz2iu2zqsXsGFZ9hh14J6yU4TkJ0BgnOY8tY3tS+n2qsw7xZfKuanSNbAo+9nkJ83i20+FwhfbJeDVOllXsdxmDWauYcSRgS9+yG5qHwUDjAxxA0iZnOjlsnI+y09+ATeTEwbAVGgp0Qu/ceP0kjUvpu1Ty7O9MoegfrmLPxdjUh3mJL+XhARby+Ax8iBckf6BQdn9W+DMlvmlzYLuLlIy7YociFOIvXvEiYYCMboVk8BLHbnw3Zmr5us3xbjtXL67L96F15acJXkM5BOmTaUbBkYGdCI+Et8XmlpbuE3xVQwmxryc2y4wP3ByuuP8GogPZz8OpPaBv8diWWUTrC2nnLhdNUrJRTKc9FepDvwHTDwfbbMCTSb4LhUIFkyFrw/i7GtkPi6NCCai6N47TgNsTnzZWRoVtOSLq7FsLiF29y0Gj0GHVPVYG3QOPS7Swc3UuiFAQZJx3YvpHA2geUgVBASMEL4vcDi2Dw3NPtBSC4EQEvH/uMILu6WyUwraywTeVpoqoHTqOoD84FzReKoWemJy6jyuiBieGlQIe6wY2elTaMOwEUFF5NagzPj6nauc0+aXzQN3Q72hxFAgtfORK60RRAHYZLYymIzSJcXLgRFsqrb1UoD+5Atq7TWojaLTfOyUvH9EeJvZEOilQAXrf/ALoI8ZhABQAA\"},\"imptrackers\":[\"https://i.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.ImpCounter?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=https%3A%2F%2Fqa-jboss.audiencelogy.com%2Ftn_native_prod.html\",\"https://i.tapnative.com/adx-rtb-d/servlet/WebF_AdManager.ImpTracker?price=${AUCTION_PRICE}&ids=111519,16703,468132,368852,211356,233,13,16704,1&cb=1728409547&ap=5.00000&vd=223.233.85.189,14,8&nm=0.00&GUIDs=[adx_guid],652c9a4c-66ea-4579-998b-cefe7b4cfecd,652c9a4c-66ea-4579-998b-cefe7b4cfecd,999999,-1_&info=2,-1,IN&adx_custom=&adx_custom_ex=~~~-1~~~0&cat=-1&ref=\",\"https://rtb-east.tapnative.com:9001/beacon?uid=44636f6605b06ec6d4389d6efb7e5054&cc=468132&fccap=5&nid=1\"]}}', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.tapnative.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidBannerResponse = { + 'body': { + 'id': '006ac3b3-67f0-43bf-a33a-388b2f869fef', + 'seatbid': [{ + 'bid': [{ + 'id': '049d07ed-c07e-4890-9f19-5cf41406a42d', + 'impid': '286e606ac84a09', + 'price': 0.11, + 'adid': '368853', + 'adm': 'invalid response', + 'adomain': ['google.com'], + 'iurl': 'https://cdn.tapnative.com/1_368853_1.png', + 'cid': '468133/368853', + 'crid': '368853', + 'w': 300, + 'h': 250, + 'cat': ['IAB7-19'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + invalidNativeResponse = { + 'body': { + 'id': '453ade66-9113-4944-a674-5bbdcb9808ac', + 'seatbid': [{ + 'bid': [{ + 'id': '652c9a4c-66ea-4579-998b-cefe7b4cfecd', + 'impid': '2c3875bdbb1893', + 'price': 1.1, + 'adid': '368852', + 'adm': 'invalid response', + 'adomain': ['www.diabetesincontrol.com'], + 'iurl': 'https://cdn.tapnative.com/1_368852_0.png', + 'cid': '468132/368852', + 'crid': '368852', + 'cat': ['IAB7'] + }], + 'seat': 'tapnative', + 'group': 0 + }], + 'cur': 'USD', + 'bidid': 'BIDDER_-1' + } + }; + }); + + describe('validations', function () { + it('isBidValid : placement_id is passed', function () { + let bid = { + bidder: 'tapnative', + params: { + placement_id: 111520 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(true); + }); + it('isBidValid : placement_id is not passed', function () { + let bid = { + bidder: 'tapnative', + params: { + width: 300, + height: 250, + domain: '', + bid_floor: 0.5 + } + }, + isValid = spec.isBidRequestValid(bid); + expect(isValid).to.equals(false); + }); + }); + describe('Validate Banner Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(bannerRequest), + bidRequest = spec.buildRequests(bannerRequest); + expect(bannerRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(bannerRequest); + expect(_Request.url).to.equal('https://rtb-east.tapnative.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(bannerRequest[0].bidId); + expect(data[0].placementId).to.equal(111520); + }); + it('Validate bid request : ad size', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].imp[0].banner).to.be.a('object'); + expect(data[0].imp[0].banner.w).to.equal(300); + expect(data[0].imp[0].banner.h).to.equal(250); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(bannerRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(bannerRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate banner response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(bannerRequest); + let bResponse = spec.interpretResponse(bannerResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(bannerResponse.body.seatbid[0].bid[0].impid); + expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('banner'); + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['google.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(bannerResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(bannerResponse.body.seatbid[0].bid[0].dealId); + }); + it('Invalid bid response check ', function () { + let bRequest = spec.buildRequests(bannerRequest); + let response = spec.interpretResponse(invalidBannerResponse, bRequest); + expect(response[0].ad).to.equal('invalid response'); + }); + }); + describe('Validate Native Request', function () { + it('Immutable bid request validate', function () { + let _Request = utils.deepClone(nativeRequest), + bidRequest = spec.buildRequests(nativeRequest); + expect(nativeRequest).to.deep.equal(_Request); + }); + it('Validate bidder connection', function () { + let _Request = spec.buildRequests(nativeRequest); + expect(_Request.url).to.equal('https://rtb-east.tapnative.com/hb'); + expect(_Request.method).to.equal('POST'); + expect(_Request.options.contentType).to.equal('application/json'); + }); + it('Validate bid request : Impression', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + // expect(data.at).to.equal(1); // auction type + expect(data[0].imp[0].id).to.equal(nativeRequest[0].bidId); + expect(data[0].placementId).to.equal(111519); + }); + it('Validate bid request : user object', function () { + let _Request = spec.buildRequests(nativeRequest); + let data = JSON.parse(_Request.data); + expect(data[0].user).to.be.a('object'); + expect(data[0].user.id).to.be.a('string'); + }); + it('Validate bid request : CCPA Check', function () { + let bidRequest = { + uspConsent: '1NYN' + }; + let _Request = spec.buildRequests(nativeRequest, bidRequest); + let data = JSON.parse(_Request.data); + expect(data[0].regs.ext.us_privacy).to.equal('1NYN'); + // let _bidRequest = {}; + // let _Request1 = spec.buildRequests(request, _bidRequest); + // let data1 = JSON.parse(_Request1.data); + // expect(data1.regs).to.equal(undefined); + }); + }); + describe('Validate native response ', function () { + it('Validate bid response : valid bid response', function () { + let _Request = spec.buildRequests(nativeRequest); + let bResponse = spec.interpretResponse(nativeResponse, _Request); + expect(bResponse).to.be.an('array').with.length.above(0); + expect(bResponse[0].requestId).to.equal(nativeResponse.body.seatbid[0].bid[0].impid); + // expect(bResponse[0].width).to.equal(bannerResponse.body.seatbid[0].bid[0].w); + // expect(bResponse[0].height).to.equal(bannerResponse.body.seatbid[0].bid[0].h); + expect(bResponse[0].currency).to.equal('USD'); + expect(bResponse[0].netRevenue).to.equal(false); + expect(bResponse[0].mediaType).to.equal('native'); + expect(bResponse[0].native.clickUrl).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.impressionTrackers).to.be.an('array').with.length.above(0); + expect(bResponse[0].native.title).to.be.a('string').and.not.be.empty; + expect(bResponse[0].native.image.url).to.be.a('string').and.not.be.empty; + expect(bResponse[0].meta.advertiserDomains).to.deep.equal(['www.diabetesincontrol.com']); + expect(bResponse[0].ttl).to.equal(300); + expect(bResponse[0].creativeId).to.equal(nativeResponse.body.seatbid[0].bid[0].crid); + expect(bResponse[0].dealId).to.equal(nativeResponse.body.seatbid[0].bid[0].dealId); + }); + }); + describe('GPP and coppa', function () { + it('Request params check with GPP Consent', function () { + let bidderReq = { gppConsent: { gppString: 'gpp-string-test', applicableSections: [5] } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-string-test'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it('Request params check with GPP Consent read from ortb2', function () { + let bidderReq = { + ortb2: { + regs: { + gpp: 'gpp-test-string', + gpp_sid: [5] + } + } + }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.gpp).to.equal('gpp-test-string'); + expect(data[0].regs.gpp_sid[0]).to.equal(5); + }); + it(' Bid request should have coppa flag if its true', () => { + let bidderReq = { ortb2: { regs: { coppa: 1 } } }; + let _Request = spec.buildRequests(bannerRequest, bidderReq); + let data = JSON.parse(_Request.data); + expect(data[0].regs.coppa).to.equal(1); + }); + }); +}); diff --git a/test/spec/modules/targetVideoAdServerVideo_spec.js b/test/spec/modules/targetVideoAdServerVideo_spec.js new file mode 100644 index 00000000000..c4c2e34b73f --- /dev/null +++ b/test/spec/modules/targetVideoAdServerVideo_spec.js @@ -0,0 +1,179 @@ +import {expect} from 'chai'; +import {buildVideoUrl} from 'modules/targetVideoAdServerVideo.js'; +import {targeting} from 'src/targeting.js'; +import * as utils from 'src/utils.js'; +import {hook} from '../../../src/hook.js'; +import AD_UNIT from 'test/fixtures/video/adUnit.json'; + +describe('TargetVideo Ad Server Video', function() { + before(() => { + hook.ready(); + }); + + let sandbox, bid, adUnit; + const unitUrl = { iu: 'https://example.com/ads/bid?iu=/video' }; + const unitId = { iu: '/video' }; + const allTargeting = { + 'hb_format': 'video', + 'hb_source': 'client', + 'hb_size': '640x480', + 'hb_pb': '5.00', + 'hb_adid': '2c4f6cc3ba128a', + 'hb_bidder': 'testBidder2', + 'hb_format_testBidder2': 'video', + 'hb_source_testBidder2': 'client', + 'hb_size_testBidder2': '640x480', + 'hb_pb_testBidder2': '5.00', + 'hb_adid_testBidder2': '2c4f6cc3ba128a', + 'hb_bidder_testBidder2': 'testBidder2', + 'hb_format_targetVideo': 'video', + 'hb_source_targetVideo': 'client', + 'hb_size_targetVideo': '640x480', + 'hb_pb_targetVideo': '5.00', + 'hb_adid_targetVideo': '44e0b5f2e5cace', + 'hb_bidder_targetVideo': 'targetVideo' + }; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + bid = { + videoCacheKey: '123', + adserverTargeting: { + hb_adid: 'ad_id', + hb_cache_id: '123', + hb_uuid: '123', + }, + }; + adUnit = utils.deepClone(AD_UNIT); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('should return undefined if required properties are missing', () => { + const url1 = buildVideoUrl({ params: {} }); + const url2 = buildVideoUrl({ adUnit: {} }); + const url3 = buildVideoUrl({ bid: {} }); + + expect(url1).to.be.undefined; + expect(url2).to.be.undefined; + expect(url3).to.be.undefined; + }); + + it('should require options.adUnit or options.bid', () => { + const options = { + params: { ...unitUrl } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(options); + expect(url1).to.be.undefined; + + const url2 = buildVideoUrl(Object.assign(options, { adUnit })); + expect(url2).to.be.string; + + const url3 = buildVideoUrl(Object.assign(options, { bid })); + expect(url3).to.be.string; + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should build URL correctly with valid parameters', () => { + const optionsUrl = { + params: { ...unitUrl } + }; + + const optionsId = { + params: { ...unitId } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(Object.assign(optionsUrl, { bid, adUnit })); + const url2 = buildVideoUrl(Object.assign(optionsId, { bid, adUnit })); + + expect(url1).to.include('https://example.com/ads/bid?iu=/video'); + expect(url1).to.include('hb_adid=ad_id'); + expect(url1).to.include('hb_cache_id=123'); + expect(url1).to.include('hb_uuid=123'); + + expect(url2).to.include('https://vid.tvserve.io/ads/bid?iu=/video'); + expect(url2).to.include('hb_adid=ad_id'); + expect(url2).to.include('hb_cache_id=123'); + expect(url2).to.include('hb_uuid=123'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should include default query parameters', () => { + const options = { + params: { ...unitUrl } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url = buildVideoUrl(Object.assign(options, { bid, adUnit })); + + expect(url).to.include('autoplay=[autoplay]'); + expect(url).to.include('mute=[vpmute]'); + expect(url).to.include('page_url=[page_url]'); + expect(url).to.include('cachebuster=[timestamp]'); + expect(url).to.include('gdpr_consent=[consent]'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); + + it('should add cust_params correctly', () => { + const optionsUrl = { + params: { + ...unitUrl, + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const optionsId = { + params: { + ...unitId, + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const optionsToMergeCustParams = { + params: { + iu: 'https://example.com/ads/bid?iu=/video&cust_params=targeting_1%3Dbaz', + cust_params: { + targeting_1: 'foo', + targeting_2: 'bar' + } + } + }; + + const getWinningBidsStub = sandbox.stub(targeting, 'getWinningBids').returns([bid]); + const getAllTargetingDataStub = sandbox.stub(targeting, 'getAllTargeting').returns(allTargeting); + + const url1 = buildVideoUrl(Object.assign(optionsUrl, { bid, adUnit })); + const url2 = buildVideoUrl(Object.assign(optionsId, { bid, adUnit })); + const url3 = buildVideoUrl(Object.assign(optionsToMergeCustParams, { bid, adUnit })); + + expect(url1).to.include('cust_params=targeting_1%3Dfoo%26targeting_2%3Dbar'); + expect(url2).to.include('cust_params=targeting_1%3Dfoo%26targeting_2%3Dbar'); + expect(url3).to.include('cust_params=targeting_1%3Dbaz%26targeting_2%3Dbar'); + + getWinningBidsStub.restore(); + getAllTargetingDataStub.restore(); + }); +}); diff --git a/test/spec/modules/targetVideoBidAdapter_spec.js b/test/spec/modules/targetVideoBidAdapter_spec.js index 8180183e6d7..61df5413862 100644 --- a/test/spec/modules/targetVideoBidAdapter_spec.js +++ b/test/spec/modules/targetVideoBidAdapter_spec.js @@ -1,27 +1,43 @@ import { spec } from '../../../modules/targetVideoBidAdapter.js' +import { SYNC_URL } from '../../../libraries/targetVideoUtils/constants.js'; describe('TargetVideo Bid Adapter', function() { + const bidder = 'targetVideo'; + const params = { + placementId: 12345, + }; + const bannerRequest = [{ - bidder: 'targetVideo', + bidder, + params, mediaTypes: { banner: { sizes: [[300, 250]], } }, - params: { - placementId: 12345, + }]; + + const videoRequest = [{ + bidder, + params, + mediaTypes: { + video: { + playerSize: [[640, 360]], + context: 'instream', + playbackmethod: [1, 2, 3, 4] + } } }]; it('Test the bid validation function', function() { - const validBid = spec.isBidRequestValid(bannerRequest[0]); + const validBid = spec.isBidRequestValid(bannerRequest[0]) && spec.isBidRequestValid(videoRequest[0]); const invalidBid = spec.isBidRequestValid(null); expect(validBid).to.be.true; expect(invalidBid).to.be.false; }); - it('Test the request processing function', function () { + it('Test the BANNER request processing function', function() { const request = spec.buildRequests(bannerRequest, bannerRequest[0]); expect(request).to.not.be.empty; @@ -36,7 +52,20 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.tags[0].ad_types[0]).to.equal('video'); }); - it('Handle nobid responses', function () { + it('Test the VIDEO request processing function', function() { + const request = spec.buildRequests(videoRequest, videoRequest[0]); + expect(request).to.not.be.empty; + + const payload = JSON.parse(request[0].data); + expect(payload).to.not.be.empty; + expect(payload.sdk).to.deep.equal({ + source: 'pbjs', + version: '$prebid.version$' + }); + expect(payload.imp[0].ext.prebid.storedrequest.id).to.equal(12345); + }) + + it('Handle BANNER nobid responses', function() { const responseBody = { 'version': '0.0.1', 'tags': [{ @@ -48,11 +77,24 @@ describe('TargetVideo Bid Adapter', function() { }; const bidderRequest = null; - const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); expect(bidResponse.length).to.equal(0); }); - it('Test the response parsing function', function () { + it('Handle VIDEO nobid responses', function() { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [], + 'nbr': 0 + }; + const bidderRequest = null; + + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); + expect(bidResponse.length).to.equal(0); + }) + + it('Test the BANNER response parsing function', function() { const responseBody = { 'tags': [{ 'uuid': '84ab500420319d', @@ -82,7 +124,7 @@ describe('TargetVideo Bid Adapter', function() { }] }; - const bidResponse = spec.interpretResponse({ body: responseBody }, {bidderRequest}); + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); expect(bidResponse).to.not.be.empty; const bid = bidResponse[0]; @@ -94,7 +136,43 @@ describe('TargetVideo Bid Adapter', function() { expect(bid.ad).to.include('initPlayer') }); - it('Test GDPR consent information is present in the request', function () { + it('Test the VIDEO response parsing function', function() { + const responseBody = { + 'id': 'test-id', + 'cur': 'USD', + 'seatbid': [{ + 'bid': [{ + 'id': '5044997188309660254', + 'price': 10, + 'adm': 'test ad', + 'adid': '97517771', + 'crid': '97517771', + 'adomain': ['domain.com'], + 'w': 640, + 'h': 480 + }], + 'seat': 'bidder' + }] + }; + const bidderRequest = { + bidderCode: 'brid', + bidderRequestId: '22edbae2733bf6', + bids: videoRequest + }; + + const bidResponse = spec.interpretResponse({ body: responseBody }, { bidderRequest }); + expect(bidResponse).to.not.be.empty; + + const bid = bidResponse[0]; + expect(bid).to.not.be.empty; + expect(bid.ad).to.equal('test ad'); + expect(bid.cpm).to.equal(10); + expect(bid.width).to.equal(640); + expect(bid.height).to.equal(480); + expect(bid.currency).to.equal('USD'); + }) + + it('Test BANNER GDPR consent information is present in the request', function() { let consentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; let bidderRequest = { 'bidderCode': 'targetVideo', @@ -119,7 +197,7 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.gdpr_consent.addtl_consent).to.exist.and.to.deep.equal([7, 12, 35, 62, 66, 70, 89, 93, 108]); }); - it('Test US Privacy string is present in the request', function() { + it('Test BANNER US Privacy string is present in the request', function() { let consentString = '1YA-'; let bidderRequest = { 'bidderCode': 'targetVideo', @@ -136,4 +214,47 @@ describe('TargetVideo Bid Adapter', function() { expect(payload.us_privacy).to.exist; expect(payload.us_privacy).to.exist.and.to.equal(consentString); }); + + it('Test VIDEO GDPR and USP consents are present in the request', function() { + let gdprConsentString = 'BOJ8RZsOJ8RZsABAB8AAAAAZ+A=='; + let uspConsentString = '1YA-'; + let bidderRequest = { + 'bidderCode': 'targetVideo', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'uspConsent': uspConsentString, + 'gdprConsent': { + consentString: gdprConsentString, + gdprApplies: true, + addtlConsent: '1~7.12.35.62.66.70.89.93.108' + } + }; + bidderRequest.bids = videoRequest; + + const request = spec.buildRequests(videoRequest, bidderRequest); + const payload = JSON.parse(request[0].data); + + expect(payload.user.ext.consent).to.equal(gdprConsentString); + expect(payload.regs.ext.us_privacy).to.equal(uspConsentString); + expect(payload.regs.ext.gdpr).to.equal(1); + }); + + it('Test userSync have only one object and it should have a property type=iframe', function () { + let userSync = spec.getUserSyncs({ iframeEnabled: true }); + expect(userSync).to.be.an('array'); + expect(userSync.length).to.be.equal(1); + expect(userSync[0]).to.have.property('type'); + expect(userSync[0].type).to.be.equal('iframe'); + }); + + it('Test userSync valid sync url for iframe', function () { + let [userSync] = spec.getUserSyncs({ iframeEnabled: true }, {}, {consentString: 'anyString'}); + expect(userSync.url).to.contain(SYNC_URL + 'load-cookie.html?endpoint=targetvideo&gdpr=0&gdpr_consent=anyString'); + expect(userSync.type).to.be.equal('iframe'); + }); + + it('Test userSyncs iframeEnabled=false', function () { + let userSyncs = spec.getUserSyncs({iframeEnabled: false}); + expect(userSyncs).to.have.lengthOf(0); + }); }); diff --git a/test/spec/modules/gdprEnforcement_spec.js b/test/spec/modules/tcfControl_spec.js similarity index 95% rename from test/spec/modules/gdprEnforcement_spec.js rename to test/spec/modules/tcfControl_spec.js index 4caf0276874..bdb14f6e44e 100644 --- a/test/spec/modules/gdprEnforcement_spec.js +++ b/test/spec/modules/tcfControl_spec.js @@ -1,18 +1,19 @@ import { accessDeviceRule, + ACTIVE_RULES, enrichEidsRule, fetchBidsRule, - transmitEidsRule, - transmitPreciseGeoRule, getGvlid, getGvlidFromAnalyticsAdapter, - ACTIVE_RULES, reportAnalyticsRule, setEnforcementConfig, STRICT_STORAGE_ENFORCEMENT, - syncUserRule, ufpdRule, + syncUserRule, + transmitEidsRule, + transmitPreciseGeoRule, + ufpdRule, validateRules -} from 'modules/gdprEnforcement.js'; +} from 'modules/tcfControl.js'; import {config} from 'src/config.js'; import adapterManager, {gdprDataHandler} from 'src/adapterManager.js'; import * as utils from 'src/utils.js'; @@ -26,8 +27,7 @@ import * as events from 'src/events.js'; import 'modules/appnexusBidAdapter.js'; // some tests expect this to be in the adapter registry import 'src/prebid.js'; import {hook} from '../../../src/hook.js'; -import {GDPR_GVLIDS, VENDORLESS_GVLID, FIRST_PARTY_GVLID} from '../../../src/consentHandler.js'; -import {validateStorageEnforcement} from '../../../src/storageManager.js'; +import {GDPR_GVLIDS, VENDORLESS_GVLID} from '../../../src/consentHandler.js'; import {activityParams} from '../../../src/activities/activityParams.js'; describe('gdpr enforcement', function () { @@ -762,47 +762,52 @@ describe('gdpr enforcement', function () { }) }) - describe('when module does not need vendor consent', () => { + describe('first party modules', () => { Object.entries({ - 'storage': 1, - 'basicAds': 2, - 'measurement': 7, - 'personalizedAds': 4, - }).forEach(([purpose, purposeNo]) => { - describe(`for purpose ${purpose}`, () => { + 'storage': { + purposeNo: 1, + allowsLI: false + }, + 'basicAds': { + purposeNo: 2, + allowsLI: true + }, + 'measurement': { + purposeNo: 7, + allowsLI: true + }, + 'personalizedAds': { + purposeNo: 4, + allowsLI: false + }, + }).forEach(([purpose, {purposeNo, allowsLI}]) => { + describe(`purpose ${purpose}`, () => { + let consent; + beforeEach(() => { + consent = utils.deepClone(consentData); + }) const rule = createGdprRule(purpose); Object.entries({ 'allowed': true, 'not allowed': false }).forEach(([t, consentGiven]) => { - it(`should be ${t} when purpose is ${t}`, () => { - const consent = utils.deepClone(consentData); - consent.vendorData.purpose.consents[purposeNo] = consentGiven; - // take legitimate interest out of the picture for this test - consent.vendorData.purpose.legitimateInterests = {}; + it(`should be ${t} when publisher is ${t}`, () => { + consent.vendorData.publisher.consents[purposeNo] = consentGiven; + consent.vendorData.publisher.legitimateInterests[purposeNo] = false; const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID); expect(actual).to.equal(consentGiven); - }) + }); + }) + it(`should ${allowsLI ? '' : 'NOT '}be allowed when publisher consent is not given, but LI is`, () => { + consent.vendorData.publisher.consents[purposeNo] = false; + consent.vendorData.publisher.legitimateInterests[purposeNo] = true; + const actual = validateRules(rule, consent, 'mockModule', VENDORLESS_GVLID); + expect(actual).to.equal(allowsLI); }) }) }) }) - it('if validateRules is passed FIRST_PARTY_GVLID, it will use publisher.consents', () => { - const rule = createGdprRule(); - const consentData = { - 'vendorData': { - 'publisher': { - 'consents': { - '1': true - } - }, - }, - }; - const result = validateRules(rule, consentData, 'cdep', FIRST_PARTY_GVLID); - expect(result).to.equal(true); - }); - describe('validateRules', function () { Object.entries({ '1 (which does not consider LI)': [1, 'storage', false], diff --git a/test/spec/modules/teadsBidAdapter_spec.js b/test/spec/modules/teadsBidAdapter_spec.js index 81e09b09d08..71ed9a21efb 100644 --- a/test/spec/modules/teadsBidAdapter_spec.js +++ b/test/spec/modules/teadsBidAdapter_spec.js @@ -44,47 +44,47 @@ describe('teadsBidAdapter', () => { }); it('should return false when pageId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 1234, 'pageId': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 'FCP', 'pageId': 1234 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId < 0 or pageId < 0', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': -1, 'pageId': -1 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function() { - let bid = Object.assign({}, bid); - delete bid.params; + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; - bid.params = { + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); @@ -142,6 +142,59 @@ describe('teadsBidAdapter', () => { expect(payload.us_privacy).to.equal(usPrivacy); }); + it('should send GPP values to endpoint when available and valid', function () { + let consentString = 'DBACNYA~CPXxRfAPXxRfAAfKABENB-CgAAAAAAAAAAYgAAAAAAAA~1YNN'; + let applicableSectionIds = [7, 8]; + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': consentString, + 'applicableSections': applicableSectionIds + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(consentString); + expect(payload.gpp.applicableSectionIds).to.have.members(applicableSectionIds); + }); + + it('should send default GPP values to endpoint when available but invalid', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000, + 'gppConsent': { + 'gppString': undefined, + 'applicableSections': ['a'] + } + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.exist; + expect(payload.gpp.consentString).to.equal(''); + expect(payload.gpp.applicableSectionIds).to.have.members([]); + }); + + it('should not set the GPP object in the request sent to the endpoint when not present', function () { + let bidderRequest = { + 'auctionId': '1d1a030790a475', + 'bidderRequestId': '22edbae2733bf6', + 'timeout': 3000 + }; + + const request = spec.buildRequests(bidRequests, bidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.gpp).to.not.exist; + }); + it('should send GDPR to endpoint', function() { let consentString = 'JRJ8RKfDeBNsERRDCSAAZ+A=='; let bidderRequest = { @@ -325,6 +378,33 @@ describe('teadsBidAdapter', () => { expect(payload.viewportHeight).to.deep.equal(window.top.visualViewport.height); }); + it('should add ortb2 device data to payload', function () { + const ortb2DeviceBidderRequest = { + ...bidderRequestDefault, + ...{ + ortb2: { + device: { + w: 980, + h: 1720, + dnt: 0, + ua: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_4 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) CriOS/125.0.6422.80 Mobile/15E148 Safari/604.1', + language: 'en', + devicetype: 1, + make: 'Apple', + model: 'iPhone 12 Pro Max', + os: 'iOS', + osv: '17.4', + ext: {fiftyonedegrees_deviceId: '17595-133085-133468-18092'}, + }, + }, + }, + }; + const request = spec.buildRequests(bidRequests, ortb2DeviceBidderRequest); + const payload = JSON.parse(request.data); + + expect(payload.device).to.deep.equal(ortb2DeviceBidderRequest.ortb2.device); + }); + it('should add hardwareConcurrency info to payload', function () { const request = spec.buildRequests(bidRequests, bidderRequestDefault); const payload = JSON.parse(request.data); diff --git a/test/spec/modules/theAdxBidAdapter_spec.js b/test/spec/modules/theAdxBidAdapter_spec.js index eb00834421a..53a65c1b044 100644 --- a/test/spec/modules/theAdxBidAdapter_spec.js +++ b/test/spec/modules/theAdxBidAdapter_spec.js @@ -81,7 +81,21 @@ describe('TheAdxAdapter', function () { [300, 600] ] } - } + }, + userId: { + uid2: { id: 'sample-uid2' }, + id5id: { + 'uid': 'sample-id5id', + 'ext': { + 'linkType': 'abc' + } + }, + netId: 'sample-netid', + sharedid: { + 'id': 'sample-sharedid', + }, + + }, }; const sampleBidderRequest = { @@ -357,6 +371,30 @@ describe('TheAdxAdapter', function () { expect(mediaTypes.video).to.not.be.null; expect(mediaTypes.video).to.not.be.undefined; }); + + it('add eids to request', function () { + let localBidRequest = JSON.parse(JSON.stringify(sampleBidRequest)); + + let results = spec.buildRequests([localBidRequest], sampleBidderRequest); + let result = results.pop(); + let payload = JSON.parse(result.data); + expect(payload).to.not.be.null; + expect(payload.ext).to.not.be.null; + + expect(payload.ext.uid2).to.not.be.null; + expect(payload.ext.uid2.length).to.greaterThan(0); + + expect(payload.ext.id5id).to.not.be.null; + expect(payload.ext.id5id.length).to.greaterThan(0); + expect(payload.ext.id5_linktype).to.not.be.null; + expect(payload.ext.id5_linktype.length).to.greaterThan(0); + + expect(payload.ext.netid).to.not.be.null; + expect(payload.ext.netid.length).to.greaterThan(0); + + expect(payload.ext.sharedid).to.not.be.null; + expect(payload.ext.sharedid.length).to.greaterThan(0); + }); }); describe('response interpreter', function () { @@ -495,7 +533,7 @@ describe('TheAdxAdapter', function () { banner: {} }, requestId: incomingRequestId, - deals: [{id: dealId}] + deals: [{ id: dealId }] }; let serverResponse = { body: sampleResponse diff --git a/test/spec/modules/tncIdSystem_spec.js b/test/spec/modules/tncIdSystem_spec.js index 57c5fa63645..4626c940a59 100644 --- a/test/spec/modules/tncIdSystem_spec.js +++ b/test/spec/modules/tncIdSystem_spec.js @@ -1,4 +1,7 @@ import { tncidSubModule } from 'modules/tncIdSystem'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; const consentData = { gdprApplies: true, @@ -50,7 +53,6 @@ describe('TNCID tests', function () { Object.defineProperty(window, '__tnc', { value: { ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { cb() }, tncid: 'TNCID_TEST_ID_1', providerId: 'TEST_PROVIDER_ID_1', }, @@ -68,8 +70,8 @@ describe('TNCID tests', function () { it('GDPR is OK and page has TNC script with ns: __tnc but not loaded, TNCID is assigned and returned', function () { Object.defineProperty(window, '__tnc', { value: { - ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { cb() }, + ready: async (readyFunc) => { await readyFunc() }, + getTNCID: async (name) => { return 'TNCID_TEST_ID_1' }, providerId: 'TEST_PROVIDER_ID_1', }, configurable: true @@ -79,18 +81,15 @@ describe('TNCID tests', function () { const {callback} = tncidSubModule.getId({}, { gdprApplies: false }); return callback(completeCallback).then(() => { - expect(completeCallback.calledOnceWithExactly(undefined)).to.be.true; + expect(completeCallback.calledOnceWithExactly('TNCID_TEST_ID_1')).to.be.true; }) }); it('GDPR is OK and page has TNC script with ns: __tncPbjs, TNCID is returned', function () { Object.defineProperty(window, '__tncPbjs', { value: { - ready: (readyFunc) => { readyFunc() }, - on: (name, cb) => { - window.__tncPbjs.tncid = 'TNCID_TEST_ID_2'; - cb(); - }, + ready: async (readyFunc) => { await readyFunc() }, + getTNCID: async (name) => { return 'TNCID_TEST_ID_2' }, providerId: 'TEST_PROVIDER_ID_1', options: {}, }, @@ -106,4 +105,23 @@ describe('TNCID tests', function () { }) }); }); + describe('eid', () => { + before(() => { + attachIdSystem(tncidSubModule); + }); + it('tncid', function() { + const userId = { + tncid: 'TEST_TNCID' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'thenewco.it', + uids: [{ + id: 'TEST_TNCID', + atype: 3 + }] + }); + }); + }); }); diff --git a/test/spec/modules/topLevelPaapi_spec.js b/test/spec/modules/topLevelPaapi_spec.js new file mode 100644 index 00000000000..d3c7966b235 --- /dev/null +++ b/test/spec/modules/topLevelPaapi_spec.js @@ -0,0 +1,501 @@ +import { + addPaapiConfigHook, + getPAAPIConfig, + registerSubmodule, + reset as resetPaapi +} from '../../../modules/paapi.js'; +import {config} from 'src/config.js'; +import {BID_STATUS, EVENTS} from 'src/constants.js'; +import * as events from 'src/events.js'; +import { + getPaapiAdId, + getPAAPIBids, + getRenderingDataHook, markWinningBidHook, + parsePaapiAdId, + parsePaapiSize, resizeCreativeHook, + topLevelPAAPI +} from '/modules/topLevelPaapi.js'; +import {auctionManager} from '../../../src/auctionManager.js'; +import {expect} from 'chai/index.js'; +import {getBidToRender} from '../../../src/adRendering.js'; + +describe('topLevelPaapi', () => { + let sandbox, auctionConfig, next, auctionId, auctions; + before(() => { + resetPaapi(); + }); + beforeEach(() => { + registerSubmodule(topLevelPAAPI); + }); + afterEach(() => { + resetPaapi(); + }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + auctions = {}; + sandbox.stub(auctionManager.index, 'getAuction').callsFake(({auctionId}) => auctions[auctionId]?.auction); + next = sinon.stub(); + auctionId = 'auct'; + auctionConfig = { + seller: 'mock.seller' + }; + config.setConfig({ + paapi: { + enabled: true, + defaultForSlots: 1 + } + }); + }); + afterEach(() => { + config.resetConfig(); + sandbox.restore(); + }); + + function addPaapiConfig(adUnitCode, auctionConfig, _auctionId = auctionId) { + let auction = auctions[_auctionId]; + if (!auction) { + auction = auctions[_auctionId] = { + auction: {}, + adUnits: {} + }; + } + if (!auction.adUnits.hasOwnProperty(adUnitCode)) { + auction.adUnits[adUnitCode] = { + code: adUnitCode, + ortb2Imp: { + ext: { + paapi: { + requestedSize: { + width: 123, + height: 321 + } + } + } + } + }; + } + addPaapiConfigHook(next, {adUnitCode, auctionId: _auctionId}, { + config: { + ...auctionConfig, + auctionId: _auctionId, + adUnitCode + } + }); + } + + function endAuctions() { + Object.entries(auctions).forEach(([auctionId, {adUnits}]) => { + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: Object.keys(adUnits), adUnits: Object.values(adUnits)}); + }); + } + + describe('when configured', () => { + let auctionConfig; + beforeEach(() => { + auctionConfig = { + seller: 'top.seller', + decisionLogicURL: 'https://top.seller/decision-logic.js' + }; + config.mergeConfig({ + paapi: { + topLevelSeller: { + auctionConfig, + autorun: false + } + } + }); + }); + + it('should augment config returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.match(getPAAPIConfig().au, auctionConfig); + }); + + it('should not choke if auction config is not defined', () => { + const cfg = config.getConfig('paapi'); + delete cfg.topLevelSeller.auctionConfig; + config.setConfig(cfg); + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.componentAuctions).to.exist; + }); + + it('should default resolveToConfig: false', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig()['au'].resolveToConfig).to.eql(false); + }); + + describe('when autoRun is set', () => { + let origRaa; + beforeEach(() => { + origRaa = navigator.runAdAuction; + navigator.runAdAuction = sinon.stub(); + }); + afterEach(() => { + navigator.runAdAuction = origRaa; + }); + + it('should start auctions automatically, when autoRun is set', () => { + config.mergeConfig({ + paapi: { + topLevelSeller: { + autorun: true + } + } + }) + addPaapiConfig('au', auctionConfig); + endAuctions(); + sinon.assert.called(navigator.runAdAuction); + }); + }); + + describe('getPAAPIBids', () => { + Object.entries({ + 'a string URN': { + pack: (val) => val, + unpack: (urn) => ({urn}), + canRender: true, + }, + 'a frameConfig object': { + pack: (val) => ({val}), + unpack: (val) => ({frameConfig: {val}}), + canRender: false + } + }).forEach(([t, {pack, unpack, canRender}]) => { + describe(`when runAdAuction returns ${t}`, () => { + let raa; + beforeEach(() => { + raa = sinon.stub().callsFake((cfg) => { + const {auctionId, adUnitCode} = cfg.componentAuctions[0]; + return Promise.resolve(pack(`raa-${adUnitCode}-${auctionId}`)); + }); + }); + + function getBids(filters) { + return getPAAPIBids(filters, raa); + } + + function expectBids(actual, expected) { + expect(Object.keys(actual)).to.eql(Object.keys(expected)); + Object.entries(expected).forEach(([au, val]) => { + sinon.assert.match(actual[au], val == null ? val : { + adId: sinon.match(val => parsePaapiAdId(val)[1] === au), + width: 123, + height: 321, + source: 'paapi', + ...unpack(val) + }); + }); + } + + describe('with one auction config', () => { + beforeEach(() => { + addPaapiConfig('au', auctionConfig, 'auct'); + endAuctions(); + }); + it('should resolve to raa result', () => { + return getBids({adUnitCode: 'au', auctionId}).then(result => { + sinon.assert.calledWith(raa, sinon.match({ + ...auctionConfig, + componentAuctions: sinon.match(cmp => cmp.find(cfg => sinon.match(cfg, auctionConfig))) + })); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + Object.entries({ + 'returns null': () => Promise.resolve(), + 'throws': () => { throw new Error() }, + 'rejects': () => Promise.reject(new Error()) + }).forEach(([t, behavior]) => { + it('should resolve to null when runAdAuction returns null', () => { + raa = sinon.stub().callsFake(behavior); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + expectBids(result, {au: null}); + }); + }); + }) + + it('should resolve to the same result when called again', () => { + getBids({adUnitCode: 'au', auctionId}); + return getBids({adUnitCode: 'au', auctionId: 'auct'}).then(result => { + sinon.assert.calledOnce(raa); + expectBids(result, {au: 'raa-au-auct'}); + }); + }); + + describe('events', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('should fire PAAPI_RUN_AUCTION', () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}), + getBids({adUnitCode: 'other', auctionId}) + ]).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'au', + auctionId, + auctionConfig: sinon.match(auctionConfig) + }); + sinon.assert.neverCalledWith(events.emit, EVENTS.RUN_PAAPI_AUCTION, { + adUnitCode: 'other' + }); + }); + }); + it('should fire PAAPI_BID', () => { + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_BID, sinon.match({ + ...unpack('raa-au-auct'), + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + it('should fire PAAPI_NO_BID', () => { + raa = sinon.stub().callsFake(() => Promise.resolve(null)); + return getBids({adUnitCode: 'au', auctionId}).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_NO_BID, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct' + })); + }); + }); + + it('should fire PAAPI_ERROR', () => { + raa = sinon.stub().callsFake(() => Promise.reject(new Error('message'))); + return getBids({adUnitCode: 'au', auctionId}).then(res => { + expect(res).to.eql({au: null}); + sinon.assert.calledWith(events.emit, EVENTS.PAAPI_ERROR, sinon.match({ + adUnitCode: 'au', + auctionId: 'auct', + error: sinon.match({message: 'message'}) + })); + }); + }); + }); + + it('should hook into getBidToRender', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then(bidToRender => [res.au, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid) + } else { + expect(bidToRender).to.not.exist; + } + }); + }); + + describe('when overrideWinner is set', () => { + let mockContextual; + beforeEach(() => { + mockContextual = { + adId: 'mock', + adUnitCode: 'au' + } + sandbox.stub(auctionManager, 'findBidByAdId').returns(mockContextual); + config.mergeConfig({ + paapi: { + topLevelSeller: { + overrideWinner: true + } + } + }); + }); + + it(`should ${!canRender ? 'NOT' : ''} override winning bid for the same adUnit`, () => { + return Promise.all([ + getBids({adUnitCode: 'au', auctionId}).then(res => res.au), + getBidToRender(mockContextual.adId) + ]).then(([paapiBid, bidToRender]) => { + if (canRender) { + expect(bidToRender).to.eql(paapiBid); + expect(paapiBid.overriddenAdId).to.eql(mockContextual.adId); + } else { + expect(bidToRender).to.eql(mockContextual) + } + }) + }); + + it('should not override when the ad unit has no paapi winner', () => { + mockContextual.adUnitCode = 'other'; + return getBidToRender(mockContextual.adId).then(bidToRender => { + expect(bidToRender).to.eql(mockContextual); + }) + }); + + it('should not override when already a paapi bid', () => { + return getBids({adUnitCode: 'au', auctionId}).then(res => { + return getBidToRender(res.au.adId).then((bidToRender) => [bidToRender, res.au]); + }).then(([bidToRender, paapiBid]) => { + expect(bidToRender).to.eql(canRender ? paapiBid : mockContextual) + }) + }); + + if (canRender) { + it('should not not override when the bid was already rendered', () => { + getBids(); + return getBidToRender(mockContextual.adId).then((bid) => { + // first pass - paapi wins over contextual + expect(bid.source).to.eql('paapi'); + bid.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false).then(bidToRender => [bid, bidToRender]) + }).then(([paapiBid, bidToRender]) => { + // if `forRender` = false (bit retrieved for x-domain events and such) + // the referenced bid is still paapi + expect(bidToRender).to.eql(paapiBid); + return getBidToRender(mockContextual.adId); + }).then(bidToRender => { + // second pass, paapi has been rendered, contextual should win + expect(bidToRender).to.eql(mockContextual); + bidToRender.status = BID_STATUS.RENDERED; + return getBidToRender(mockContextual.adId, false); + }).then(bidToRender => { + // if the contextual bid has been rendered, it's the one being referenced + expect(bidToRender).to.eql(mockContextual); + }); + }) + } + }); + }); + + it('should resolve the same result from different filters', () => { + const targets = { + auct1: ['au1', 'au2'], + auct2: ['au1', 'au3'] + }; + Object.entries(targets).forEach(([auctionId, adUnitCodes]) => { + adUnitCodes.forEach(au => addPaapiConfig(au, auctionConfig, auctionId)); + }); + endAuctions(); + return Promise.all( + [ + [ + {adUnitCode: 'au1', auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1' + } + ], + [ + {}, + { + au1: 'raa-au1-auct2', + au2: 'raa-au2-auct1', + au3: 'raa-au3-auct2' + } + ], + [ + {auctionId: 'auct1'}, + { + au1: 'raa-au1-auct1', + au2: 'raa-au2-auct1' + } + ], + [ + {adUnitCode: 'au1'}, + { + au1: 'raa-au1-auct2' + } + ], + ].map(([filters, expected]) => getBids(filters).then(res => [res, expected])) + ).then(res => { + res.forEach(([actual, expected]) => { + expectBids(actual, expected); + }); + }); + }); + }); + }); + }); + }); + + describe('when not configured', () => { + it('should not alter configs returned by getPAAPIConfig', () => { + addPaapiConfig('au', auctionConfig); + endAuctions(); + expect(getPAAPIConfig().au.seller).to.not.exist; + }); + }); + + describe('paapi adId', () => { + [ + ['auctionId', 'adUnitCode'], + ['auction:id', 'adUnit:code'], + ['auction:uid', 'ad:unit'] + ].forEach(([auctionId, adUnitCode]) => { + it(`can encode and decode ${auctionId}, ${adUnitCode}`, () => { + expect(parsePaapiAdId(getPaapiAdId(auctionId, adUnitCode))).to.eql([auctionId, adUnitCode]); + }); + }); + + [undefined, null, 'not-a-paapi-ad', 'paapi:/malformed'].forEach(adId => { + it(`returns null for adId ${adId}`, () => { + expect(parsePaapiAdId(adId)).to.not.exist; + }); + }); + }); + + describe('parsePaapiSize', () => { + [ + [null, null], + [undefined, null], + [123, 123], + ['123', 123], + ['123px', 123], + ['1sw', null], + ['garbage', null] + ].forEach(([input, expected]) => { + it(`can parse ${input} => ${expected}`, () => { + expect(parsePaapiSize(input)).to.eql(expected); + }); + }); + }); + + describe('rendering hooks', () => { + let next; + beforeEach(() => { + next = sinon.stub() + next.bail = sinon.stub() + }); + describe('getRenderingDataHook', () => { + it('intercepts paapi bids', () => { + getRenderingDataHook(next, { + source: 'paapi', + width: 123, + height: null, + urn: 'url' + }); + sinon.assert.calledWith(next.bail, { + width: 123, + height: null, + adUrl: 'url' + }); + }); + it('does not touch non-paapi bids', () => { + getRenderingDataHook(next, {bid: 'data'}, {other: 'options'}); + sinon.assert.calledWith(next, {bid: 'data'}, {other: 'options'}); + }); + }); + + describe('markWinnigBidsHook', () => { + beforeEach(() => { + sandbox.stub(events, 'emit'); + }); + it('handles paapi bids', () => { + const bid = {source: 'paapi'}; + markWinningBidHook(next, bid); + sinon.assert.notCalled(next); + sinon.assert.called(next.bail); + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, bid); + }); + it('ignores non-paapi bids', () => { + markWinningBidHook(next, {other: 'bid'}); + sinon.assert.calledWith(next, {other: 'bid'}); + sinon.assert.notCalled(next.bail); + }); + }); + }); +}); diff --git a/test/spec/modules/topicsFpdModule_spec.js b/test/spec/modules/topicsFpdModule_spec.js index 4a79e7f77fd..0f8e379e95f 100644 --- a/test/spec/modules/topicsFpdModule_spec.js +++ b/test/spec/modules/topicsFpdModule_spec.js @@ -308,33 +308,6 @@ describe('topics', () => { }], name: 'ads.pubmatic.com' }]; - const consentString = 'CPi8wgAPi8wgAADABBENCrCsAP_AAH_AAAAAISNB7D=='; - const consentConfig = { - consentString: consentString, - gdprApplies: true, - vendorData: { - metadata: consentString, - gdprApplies: true, - purpose: { - consents: { - 1: true, - 2: true, - 3: true, - 4: true - } - } - } - }; - const mockData = [ - { - name: 'domain', - segment: [{id: 123}] - }, - { - name: 'domain', - segment: [{id: 321}], - } - ]; const evt = { data: '{"segment":{"domain":"ads.pubmatic.com","topics":[{"configVersion":"chrome.1","modelVersion":"2206021246","taxonomyVersion":"1","topic":165,"version":"chrome.1:1:2206021246"}],"bidder":"pubmatic"},"date":1669743901858}', @@ -345,37 +318,60 @@ describe('topics', () => { storage.removeDataFromLocalStorage(topicStorageName); }); - describe('when cached data is available and not expired', () => { + describe('caching', () => { let sandbox; beforeEach(() => { sandbox = sinon.sandbox.create(); - const storedSegments = JSON.stringify( - [['pubmatic', { - '2206021246': { - 'ext': {'segtax': 600, 'segclass': '2206021246'}, - 'segment': [{'id': '243'}, {'id': '265'}], - 'name': 'ads.pubmatic.com' - }, - 'lastUpdated': new Date().getTime() - }]] - ); - storage.setDataInLocalStorage(topicStorageName, storedSegments); - }); + }) + afterEach(() => { sandbox.restore(); + config.resetConfig(); }); - it('should return segments for bidder if transmitUfpd is allowed', () => { - assert.deepEqual(getCachedTopics(), expected); - }); + it('should return no segments when not configured', () => { + config.setConfig({userSync: {}}); + expect(getCachedTopics()).to.eql([]); + }) + + describe('when cached data is available and not expired', () => { + beforeEach(() => { + const storedSegments = JSON.stringify( + [['pubmatic', { + '2206021246': { + 'ext': {'segtax': 600, 'segclass': '2206021246'}, + 'segment': [{'id': '243'}, {'id': '265'}], + 'name': 'ads.pubmatic.com' + }, + 'lastUpdated': new Date().getTime() + }]] + ); + storage.setDataInLocalStorage(topicStorageName, storedSegments); + config.setConfig({ + userSync: { + topics: { + maxTopicCaller: 4, + bidders: [{ + bidder: 'pubmatic', + iframeURL: 'https://ads.pubmatic.com/AdServer/js/topics/topics_frame.html' + }] + } + } + }) + }); - it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { - sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { - return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + it('should return segments for bidder if transmitUfpd is allowed', () => { + assert.deepEqual(getCachedTopics(), expected); + }); + + it('should NOT return segments for bidder if enrichUfpd is NOT allowed', () => { + sandbox.stub(activities, 'isActivityAllowed').callsFake((activity, params) => { + return !(activity === ACTIVITY_ENRICH_UFPD && params.component === 'bidder.pubmatic'); + }); + expect(getCachedTopics()).to.eql([]); }); - expect(getCachedTopics()).to.eql([]); }); - }) + }); it('should return empty segments for bidder if there is cached segments stored which is expired', () => { let storedSegments = '[["pubmatic",{"2206021246":{"ext":{"segtax":600,"segclass":"2206021246"},"segment":[{"id":"243"},{"id":"265"}],"name":"ads.pubmatic.com"},"lastUpdated":10}]]'; diff --git a/test/spec/modules/tpmnBidAdapter_spec.js b/test/spec/modules/tpmnBidAdapter_spec.js index 505bc9d878f..48295e6fdff 100644 --- a/test/spec/modules/tpmnBidAdapter_spec.js +++ b/test/spec/modules/tpmnBidAdapter_spec.js @@ -4,7 +4,7 @@ import { generateUUID } from '../../../src/utils.js'; import { expect } from 'chai'; import * as utils from 'src/utils'; import * as sinon from 'sinon'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; diff --git a/test/spec/modules/trafficgateBidAdapter_spec.js b/test/spec/modules/trafficgateBidAdapter_spec.js index 11ff547cc78..192497f9c8d 100644 --- a/test/spec/modules/trafficgateBidAdapter_spec.js +++ b/test/spec/modules/trafficgateBidAdapter_spec.js @@ -9,9 +9,11 @@ import 'modules/currency.js'; import 'modules/userId/index.js'; import 'modules/multibid/index.js'; import 'modules/priceFloors.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import 'modules/consentManagementUsp.js'; import 'modules/schain.js'; +import 'modules/paapi.js'; + import {deepClone} from 'src/utils.js'; import {syncAddFPDToBidderRequest} from '../../helpers/fpd.js'; import {hook} from '../../../src/hook.js'; @@ -193,9 +195,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); - videoBidWithMediaTypes.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaTypes)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaTypes); + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); describe('and request config uses both host and platform', () => { @@ -250,10 +252,10 @@ describe('TrafficgateOpenxRtbAdapter', function () { }); it('should return false when required params are not passed', function () { - let videoBidWithMediaType = Object.assign({}, videoBidWithMediaType); - delete videoBidWithMediaType.params; - videoBidWithMediaType.params = {}; - expect(spec.isBidRequestValid(videoBidWithMediaType)).to.equal(false); + let invalidVideoBidWithMediaTypes = Object.assign({}, videoBidWithMediaType); + delete invalidVideoBidWithMediaTypes.params; + invalidVideoBidWithMediaTypes.params = {}; + expect(spec.isBidRequestValid(invalidVideoBidWithMediaTypes)).to.equal(false); }); }); }); @@ -999,12 +1001,11 @@ describe('TrafficgateOpenxRtbAdapter', function () { bidId: 'test-bid-id-1', bidderRequestId: 'test-bid-request-1', auctionId: 'test-auction-1', - userIdAsEids: userIdAsEids }]; // enrich bid request with userId key/value - + mockBidderRequest.ortb2 = {user: {ext: {eids: userIdAsEids}}} const request = spec.buildRequests(bidRequestsWithUserId, mockBidderRequest); - expect(request[0].data.user.ext.eids).to.equal(userIdAsEids); + expect(request[0].data.user.ext.eids).to.eql(userIdAsEids); }); it(`when no user ids are available, it should not send any extended ids`, function () { @@ -1017,7 +1018,9 @@ describe('TrafficgateOpenxRtbAdapter', function () { it('when FLEDGE is enabled, should send whatever is set in ortb2imp.ext.ae in all bid requests', function () { const request = spec.buildRequests(bidRequestsWithMediaTypes, { ...mockBidderRequest, - fledgeEnabled: true + paapi: { + enabled: true + } }); expect(request[0].data.imp[0].ext.ae).to.equal(2); }); diff --git a/test/spec/modules/tripleliftBidAdapter_spec.js b/test/spec/modules/tripleliftBidAdapter_spec.js index 851425574d0..216142ab02e 100644 --- a/test/spec/modules/tripleliftBidAdapter_spec.js +++ b/test/spec/modules/tripleliftBidAdapter_spec.js @@ -167,7 +167,8 @@ describe('triplelift adapter', function () { video: { context: 'instream', playerSize: [640, 480], - playbackmethod: 5 + playbackmethod: 5, + plcmt: 1 } }, adUnitCode: 'adunit-code-instream', @@ -307,7 +308,8 @@ describe('triplelift adapter', function () { video: { context: 'instream', playerSize: [640, 480], - playbackmethod: [1, 2, 3] + playbackmethod: [1, 2, 3], + plcmt: 1 }, banner: { sizes: [ @@ -524,7 +526,8 @@ describe('triplelift adapter', function () { mediaTypes: { video: { context: 'outstream', - playerSize: [640, 480] + playerSize: [640, 480], + plcmt: 4 } }, adUnitCode: 'adunit-code-instream', @@ -553,7 +556,7 @@ describe('triplelift adapter', function () { video: { context: 'outstream', playerSize: [640, 480], - placement: 6 + plcmt: 3 } }, adUnitCode: 'adunit-code-instream', @@ -637,12 +640,12 @@ describe('triplelift adapter', function () { 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'); - expect(payload.imp[1].video.placement).to.equal(1); + expect(payload.imp[1].video.plcmt).to.equal(1); // banner and outstream video expect(payload.imp[2]).to.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}]); - expect(payload.imp[2].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[2].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // banner and incomplete video expect(payload.imp[3]).to.not.have.property('video'); expect(payload.imp[3]).to.have.property('banner'); @@ -655,21 +658,24 @@ describe('triplelift adapter', function () { 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'); - expect(payload.imp[5].video.placement).to.equal(1); + expect(payload.imp[5].video.plcmt).to.equal(1); // banner and outream video and native expect(payload.imp[6]).to.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}]); - expect(payload.imp[6].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[6].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // outstream video only expect(payload.imp[7]).to.have.property('video'); expect(payload.imp[7]).to.not.have.property('banner'); - expect(payload.imp[7].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[7].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream'}); // banner and incomplete outstream (missing size); video request is permitted so banner can still monetize expect(payload.imp[8]).to.have.property('video'); expect(payload.imp[8]).to.have.property('banner'); expect(payload.imp[8].banner.format).to.deep.equal([{w: 300, h: 250}, {w: 300, h: 600}]); - expect(payload.imp[8].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'context': 'outstream', 'placement': 3}); + expect(payload.imp[8].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'context': 'outstream'}); + // outstream new plcmt value + expect(payload.imp[13]).to.have.property('video'); + expect(payload.imp[13].video).to.deep.equal({'mimes': ['video/mp4'], 'maxduration': 30, 'minduration': 6, 'w': 640, 'h': 480, 'context': 'outstream', 'plcmt': 3}); }); it('should check for valid outstream placement values', function () { @@ -694,12 +700,12 @@ describe('triplelift adapter', function () { expect(payload.imp[12]).to.not.have.property('banner'); expect(payload.imp[12]).to.have.property('video'); expect(payload.imp[12].video).to.exist.and.to.be.a('object'); - expect(payload.imp[12].video.placement).to.equal(3); + expect(payload.imp[12].video.plcmt).to.equal(4); // outstream video; invalid placement expect(payload.imp[13]).to.not.have.property('banner'); expect(payload.imp[13]).to.have.property('video'); expect(payload.imp[13].video).to.exist.and.to.be.a('object'); - expect(payload.imp[13].video.placement).to.equal(3); + expect(payload.imp[13].video.plcmt).to.equal(3); }); it('should add tid to imp.ext if transactionId exists', function() { @@ -868,7 +874,7 @@ describe('triplelift adapter', function () { expect(url).to.match(/(\?|&)us_privacy=1YYY/); }); it('should pass fledge signal when Triplelift is eligible for fledge', function() { - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; const request = tripleliftAdapterSpec.buildRequests(bidRequests, bidderRequest); const url = request.url; expect(url).to.match(/(\?|&)fledge=true/); @@ -1405,11 +1411,11 @@ describe('triplelift adapter', function () { let result = tripleliftAdapterSpec.interpretResponse(response, {bidderRequest}); expect(result).to.have.property('bids'); - expect(result).to.have.property('fledgeAuctionConfigs'); - expect(result.fledgeAuctionConfigs.length).to.equal(2); - expect(result.fledgeAuctionConfigs[0].bidId).to.equal('30b31c1838de1e'); - expect(result.fledgeAuctionConfigs[1].bidId).to.equal('73edc0ba8de203'); - expect(result.fledgeAuctionConfigs[0].config).to.deep.equal( + expect(result).to.have.property('paapi'); + expect(result.paapi.length).to.equal(2); + expect(result.paapi[0].bidId).to.equal('30b31c1838de1e'); + expect(result.paapi[1].bidId).to.equal('73edc0ba8de203'); + expect(result.paapi[0].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', @@ -1417,7 +1423,7 @@ describe('triplelift adapter', function () { 'perBuyerSignals': { 'https://some_buyer.com': { 'a': 1 } } } ); - expect(result.fledgeAuctionConfigs[1].config).to.deep.equal( + expect(result.paapi[1].config).to.deep.equal( { 'seller': 'https://3lift.com', 'decisionLogicUrl': 'https://3lift.com/decision_logic.js', diff --git a/test/spec/modules/truereachBidAdapter_spec.js b/test/spec/modules/truereachBidAdapter_spec.js index cd7d0873569..78e6828147b 100644 --- a/test/spec/modules/truereachBidAdapter_spec.js +++ b/test/spec/modules/truereachBidAdapter_spec.js @@ -12,7 +12,6 @@ describe('truereachBidAdapterTests', function () { bidder: 'truereach', params: { site_id: '0142010a-8400-1b01-72cb-a553b9000009', - bidfloor: 0.1 } })).to.equal(true); }); @@ -27,8 +26,7 @@ describe('truereachBidAdapterTests', function () { }, bidder: 'truereach', params: { - site_id: '0142010a-8400-1b01-72cb-a553b9000009', - bidfloor: 0.1 + site_id: '0142010a-8400-1b01-72cb-a553b9000009' }, sizes: [[300, 250]] }]; @@ -40,7 +38,6 @@ 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); }); it('validate_response_params', function () { diff --git a/test/spec/modules/ttdBidAdapter_spec.js b/test/spec/modules/ttdBidAdapter_spec.js index 1fe504ba8e8..9f98a734d1c 100644 --- a/test/spec/modules/ttdBidAdapter_spec.js +++ b/test/spec/modules/ttdBidAdapter_spec.js @@ -277,6 +277,13 @@ describe('ttdBidAdapter', function () { expect(url).to.equal('https://direct.adsrvr.org/bid/bidder/supplier'); }); + it('sends bid requests to the correct custom endpoint', function () { + let bannerBidRequestsWithCustomEndpoint = deepClone(baseBannerBidRequests); + bannerBidRequestsWithCustomEndpoint[0].params.useHttp2 = true; + const url = testBuildRequests(bannerBidRequestsWithCustomEndpoint, baseBidderRequest).url; + expect(url).to.equal('https://d2.adsrvr.org/bid/bidder/supplier'); + }); + it('sends publisher id', function () { const requestBody = testBuildRequests(baseBannerBidRequests, baseBidderRequest).data; expect(requestBody.site).to.be.not.null; @@ -442,7 +449,9 @@ describe('ttdBidAdapter', function () { let clonedBannerRequests = deepClone(baseBannerBidRequests); const battr = [1, 2, 3]; clonedBannerRequests[0].ortb2Imp = { - battr: battr + banner: { + battr: battr + } }; const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest).data; expect(requestBody.imp[0].banner.battr).to.equal(battr); diff --git a/test/spec/modules/twistDigitalBidAdapter_spec.js b/test/spec/modules/twistDigitalBidAdapter_spec.js index 7d263f6d4f0..c15a6d1d909 100644 --- a/test/spec/modules/twistDigitalBidAdapter_spec.js +++ b/test/spec/modules/twistDigitalBidAdapter_spec.js @@ -2,15 +2,7 @@ import {expect} from 'chai'; import { spec as adapter, createDomain, - hashCode, - extractPID, - extractCID, - extractSubDomain, - getStorageItem, - setStorageItem, - tryParseJSON, - getUniqueDealId, - webSessionId + storage } from 'modules/twistDigitalBidAdapter.js'; import * as utils from 'src/utils.js'; import {version} from 'package.json'; @@ -18,6 +10,16 @@ import {useFakeTimers} from 'sinon'; import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; import {deepSetValue} from 'src/utils.js'; +import { + extractPID, + extractCID, + extractSubDomain, + hashCode, + getStorageItem, + setStorageItem, + tryParseJSON, + getUniqueDealId +} from '../../../libraries/vidazooUtils/bidderUtils.js'; export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; @@ -109,6 +111,7 @@ const BIDDER_REQUEST = { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'], 'content': { + 'language': 'en', 'data': [{ 'name': 'example.com', 'ext': { @@ -123,7 +126,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -335,6 +339,8 @@ describe('TwistDigitalBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -355,7 +361,6 @@ describe('TwistDigitalBidAdapter', function () { uniqueDealId: `${hashUrl}_${Date.now().toString()}`, uqs: getTopWindowQueryParams(), isStorageAllowed: true, - webSessionId: webSessionId, mediaTypes: { video: { api: [2], @@ -437,6 +442,8 @@ describe('TwistDigitalBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -453,8 +460,7 @@ describe('TwistDigitalBidAdapter', function () { name: 'example.com', segment: [{id: '243'}], }, - ], - webSessionId: webSessionId + ] } }); }); @@ -524,6 +530,8 @@ describe('TwistDigitalBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -540,8 +548,7 @@ describe('TwistDigitalBidAdapter', function () { name: 'example.com', segment: [{id: '243'}], }, - ], - webSessionId: webSessionId + ] }; const REQUEST_DATA2 = utils.deepClone(REQUEST_DATA); @@ -588,7 +595,7 @@ describe('TwistDigitalBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); @@ -738,8 +745,6 @@ describe('TwistDigitalBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -805,13 +810,13 @@ describe('TwistDigitalBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -819,7 +824,7 @@ describe('TwistDigitalBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -843,8 +848,8 @@ describe('TwistDigitalBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -855,7 +860,7 @@ describe('TwistDigitalBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/uid2IdSystem_helpers.js b/test/spec/modules/uid2IdSystem_helpers.js index e0bef047acb..44a3b374999 100644 --- a/test/spec/modules/uid2IdSystem_helpers.js +++ b/test/spec/modules/uid2IdSystem_helpers.js @@ -1,6 +1,6 @@ -import {setConsentConfig} from 'modules/consentManagement.js'; +import {setConsentConfig} from 'modules/consentManagementTcf.js'; import {server} from 'test/mocks/xhr.js'; -import {coreStorage, requestBidsHook} from 'modules/userId/index.js'; +import {coreStorage, startAuctionHook} from 'modules/userId/index.js'; const msIn12Hours = 60 * 60 * 12 * 1000; const expireCookieDate = 'Thu, 01 Jan 1970 00:00:01 GMT'; @@ -19,7 +19,7 @@ export const runAuction = async () => { bids: [{bidder: 'sampleBidder', params: {placementId: 'banner-only-bidder'}}] }]; return new Promise(function(resolve) { - requestBidsHook(function() { + startAuctionHook(function() { resolve(adUnits[0].bids[0]); }, {adUnits}); }); diff --git a/test/spec/modules/uid2IdSystem_spec.js b/test/spec/modules/uid2IdSystem_spec.js index 901e0c57e32..434bca17416 100644 --- a/test/spec/modules/uid2IdSystem_spec.js +++ b/test/spec/modules/uid2IdSystem_spec.js @@ -1,17 +1,16 @@ -/* eslint-disable no-console */ - -import {coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; +import {attachIdSystem, coreStorage, init, setSubmoduleRegistry} from 'modules/userId/index.js'; import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import { uid2IdSubmodule } from 'modules/uid2IdSystem.js'; import 'src/prebid.js'; -import 'modules/consentManagement.js'; +import 'modules/consentManagementTcf.js'; import { getGlobal } from 'src/prebidGlobal.js'; import { configureTimerInterceptors } from 'test/mocks/timers.js'; import { cookieHelpers, runAuction, apiHelpers, setGdprApplies } from './uid2IdSystem_helpers.js'; import {hook} from 'src/hook.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {server} from 'test/mocks/xhr'; +import {createEidsArray} from '../../../modules/userId/eids.js'; let expect = require('chai').expect; @@ -25,6 +24,7 @@ const initialToken = `initial-advertising-token`; const legacyToken = 'legacy-advertising-token'; const refreshedToken = 'refreshed-advertising-token'; const clientSideGeneratedToken = 'client-side-generated-advertising-token'; +const optoutToken = 'optout-token'; const legacyConfigParams = {storage: null}; const serverCookieConfigParams = { uid2ServerCookie: publisherCookieName }; @@ -32,6 +32,7 @@ const newServerCookieConfigParams = { uid2Cookie: publisherCookieName }; const cstgConfigParams = { serverPublicKey: 'UID2-X-L-24B8a/eLYBmRkXA9yPgRZt+ouKbXewG2OPs23+ov3JC8mtYJBCx6AxGwJ4MlwUcguebhdDp2CvzsCgS9ogwwGA==', subscriptionId: 'subscription-id' } const makeUid2IdentityContainer = (token) => ({uid2: {id: token}}); +const makeUid2OptoutContainer = (token) => ({uid2: {optout: true}}); let useLocalStorage = false; const makePrebidConfig = (params = null, extraSettings = {}, debug = false) => ({ userSync: { auctionDelay: auctionDelayMs, userIds: [{name: 'uid2', params: {storage: useLocalStorage ? 'localStorage' : 'cookie', ...params}}] }, debug, ...extraSettings @@ -49,6 +50,7 @@ const getFromAppropriateStorage = () => { const expectToken = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2IdentityContainer(token)); const expectLegacyToken = (bid) => expect(bid.userId).to.deep.include(makeUid2IdentityContainer(legacyToken)); const expectNoIdentity = (bid) => expect(bid).to.not.haveOwnProperty('userId'); +const expectOptout = (bid, token) => expect(bid?.userId ?? {}).to.deep.include(makeUid2OptoutContainer(token)); const expectGlobalToHaveToken = (token) => expect(getGlobal().getUserIds()).to.deep.include(makeUid2IdentityContainer(token)); const expectGlobalToHaveNoUid2 = () => expect(getGlobal().getUserIds()).to.not.haveOwnProperty('uid2'); const expectNoLegacyToken = (bid) => expect(bid.userId).to.not.deep.include(makeUid2IdentityContainer(legacyToken)); @@ -64,6 +66,7 @@ const apiUrl = 'https://prod.uidapi.com/v2/token' const refreshApiUrl = `${apiUrl}/refresh`; const headers = { 'Content-Type': 'application/json' }; const makeSuccessResponseBody = (responseToken) => btoa(JSON.stringify({ status: 'success', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: responseToken } })); +const makeOptoutResponseBody = (token) => btoa(JSON.stringify({ status: 'optout', body: { ...apiHelpers.makeTokenResponse(initialToken), advertising_token: token } })); const cstgApiUrl = `${apiUrl}/client-generate`; const testCookieAndLocalStorage = (description, test, only = false) => { @@ -90,7 +93,8 @@ describe(`UID2 module`, function () { before(function () { timerSpy = configureTimerInterceptors(debugOutput); hook.ready(); - uninstallGdprEnforcement(); + uninstallTcfControl(); + attachIdSystem(uid2IdSubmodule); suiteSandbox = sinon.sandbox.create(); // I'm unable to find an authoritative source, but apparently subtle isn't available in some test stacks for security reasons. @@ -120,6 +124,7 @@ describe(`UID2 module`, function () { const configureUid2Response = (apiUrl, httpStatus, response) => server.respondWith('POST', apiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); const configureUid2ApiSuccessResponse = (apiUrl, responseToken) => configureUid2Response(apiUrl, 200, makeSuccessResponseBody(responseToken)); const configureUid2ApiFailResponse = (apiUrl) => configureUid2Response(apiUrl, 500, 'Error'); + const configureUid2CstgResponse = (httpStatus, response) => server.respondWith('POST', cstgApiUrl, (xhr) => xhr.respond(httpStatus, headers, response)); // Runs the provided test twice - once with a successful API mock, once with one which returns a server error const testApiSuccessAndFailure = (act, apiUrl, testDescription, failTestDescription, only = false, responseToken = refreshedToken) => { const testFn = only ? it.only : it; @@ -228,7 +233,6 @@ describe(`UID2 module`, function () { const bid = await runAuction(); - console.log('Storage', coreStorage.getDataFromLocalStorage(moduleCookieName)); init(config); setSubmoduleRegistry([uid2IdSubmodule]); config.setConfig(makePrebidConfig(legacyConfigParams)); @@ -244,7 +248,6 @@ describe(`UID2 module`, function () { name: 'Token provided in config call', setConfig: (token, extraConfig = {}) => { const gen = makePrebidConfig({uid2Token: token}, extraConfig); - console.log('GENERATED CONFIG', gen.userSync.userIds[0].params); return config.setConfig(gen); }, }, @@ -442,6 +445,15 @@ describe(`UID2 module`, function () { }); }); }); + it('Should receive an optout response when the user has opted out.', async function() { + const uid2Token = apiHelpers.makeTokenResponse(initialToken, true, true); + configureUid2CstgResponse(200, makeOptoutResponseBody(optoutToken)); + config.setConfig(makePrebidConfig({ uid2Token, ...cstgConfigParams, email: 'optout@test.com' })); + apiHelpers.respondAfterDelay(1, server); + + const bid = await runAuction(); + expectOptout(bid, optoutToken); + }); describe(`when the response doesn't arrive before the auction timer`, function() { testApiSuccessAndFailure(async function() { config.setConfig(makePrebidConfig({ ...cstgConfigParams, email: 'test@test.com' })); @@ -626,5 +638,39 @@ describe(`UID2 module`, function () { const bid = await runAuction(); expectNoIdentity(bid); }) + }); + describe('eid', () => { + 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('uid2 with ext', function() { + const userId = { + uid2: {'id': 'Sample_AD_Token', 'ext': {'provider': 'some.provider.com'}} + }; + 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, + ext: { + provider: 'some.provider.com' + } + }] + }); + }); }) }); diff --git a/test/spec/modules/unifiedIdSystem_spec.js b/test/spec/modules/unifiedIdSystem_spec.js new file mode 100644 index 00000000000..9e6ce4e127b --- /dev/null +++ b/test/spec/modules/unifiedIdSystem_spec.js @@ -0,0 +1,46 @@ +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {unifiedIdSubmodule} from '../../../modules/unifiedIdSystem.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {expect} from 'chai/index.mjs'; +import {server} from 'test/mocks/xhr.js'; + +describe('Unified ID', () => { + describe('getId', () => { + it('should use provided URL', () => { + unifiedIdSubmodule.getId({params: {url: 'https://given-url/'}}).callback(); + expect(server.requests[0].url).to.eql('https://given-url/'); + }); + it('should use partner URL', () => { + unifiedIdSubmodule.getId({params: {partner: 'rubicon'}}).callback(); + expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); + }); + }); + describe('eid', () => { + before(() => { + attachIdSystem(unifiedIdSubmodule); + }); + it('unifiedId: ext generation', function () { + const userId = { + tdid: 'some-random-id-value' + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-random-id-value', atype: 1, ext: {rtiPartner: 'TDID'}}] + }); + }); + + it('unifiedId: ext generation with provider', function () { + const userId = { + tdid: {'id': 'some-sample_id', 'ext': {'provider': 'some.provider.com'}} + }; + const newEids = createEidsArray(userId); + expect(newEids.length).to.equal(1); + expect(newEids[0]).to.deep.equal({ + source: 'adserver.org', + uids: [{id: 'some-sample_id', atype: 1, ext: {rtiPartner: 'TDID', provider: 'some.provider.com'}}] + }); + }); + }); +}); diff --git a/test/spec/modules/unrulyBidAdapter_spec.js b/test/spec/modules/unrulyBidAdapter_spec.js index abf1a54787d..662e5c0e03d 100644 --- a/test/spec/modules/unrulyBidAdapter_spec.js +++ b/test/spec/modules/unrulyBidAdapter_spec.js @@ -696,7 +696,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -782,7 +784,9 @@ describe('UnrulyAdapter', function () { it('should return an array with 2 items and enabled protected audience on only one unit', function () { mockBidRequests = { 'bidderCode': 'unruly', - 'fledgeEnabled': true, + 'paapi': { + enabled: true + }, 'bids': [ { 'bidder': 'unruly', @@ -1043,7 +1047,7 @@ describe('UnrulyAdapter', function () { mediaType: 'video' } ], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', @@ -1107,7 +1111,7 @@ describe('UnrulyAdapter', function () { expect(adapter.interpretResponse(mockServerResponse, originalRequest)).to.deep.equal({ 'bids': [], - 'fledgeAuctionConfigs': [{ + 'paapi': [{ 'bidId': bidId, 'config': { 'seller': 'https://nexxen.tech', diff --git a/test/spec/modules/userId_spec.js b/test/spec/modules/userId_spec.js index 2ff19424e09..c4f333e56ac 100644 --- a/test/spec/modules/userId_spec.js +++ b/test/spec/modules/userId_spec.js @@ -1,13 +1,15 @@ import { attachIdSystem, auctionDelay, - coreStorage, dep, - findRootDomain, getConsentHash, + coreStorage, + dep, enrichEids, + findRootDomain, + getConsentHash, getValidSubmoduleConfigs, init, PBJS_USER_ID_OPTOUT_NAME, - requestBidsHook, + startAuctionHook, + addUserIdsHook, requestDataDeletion, - setStoredConsentData, setStoredValue, setSubmoduleRegistry, syncDelay, @@ -18,44 +20,19 @@ import {config} from 'src/config.js'; import * as utils from 'src/utils.js'; import {getPrebidInternal} from 'src/utils.js'; import * as events from 'src/events.js'; -import { EVENTS } from 'src/constants.js'; +import {EVENTS} from 'src/constants.js'; import {getGlobal} from 'src/prebidGlobal.js'; -import {resetConsentData, } from 'modules/consentManagement.js'; -import {server} from 'test/mocks/xhr.js'; -import {unifiedIdSubmodule} from 'modules/unifiedIdSystem.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, - setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent -} from 'modules/liveIntentIdSystem.js'; -import {merkleIdSubmodule} from 'modules/merkleIdSystem.js'; -import {netIdSubmodule} from 'modules/netIdSystem.js'; -import {intentIqIdSubmodule} from 'modules/intentIqIdSystem.js'; -import {zeotapIdPlusSubmodule} from 'modules/zeotapIdPlusIdSystem.js'; +import {resetConsentData, } from 'modules/consentManagementTcf.js'; +import {setEventFiredFlag as liveIntentIdSubmoduleDoNotFireEvent} from '../../../libraries/liveIntentId/idSystem.js'; import {sharedIdSystemSubmodule} from 'modules/sharedIdSystem.js'; -import {hadronIdSubmodule} from 'modules/hadronIdSystem.js'; import {pubProvidedIdSubmodule} from 'modules/pubProvidedIdSystem.js'; -import {criteoIdSubmodule} from 'modules/criteoIdSystem.js'; -import {mwOpenLinkIdSubModule} from 'modules/mwOpenLinkIdSystem.js'; -import {tapadIdSubmodule} from 'modules/tapadIdSystem.js'; -import {tncidSubModule} from 'modules/tncIdSystem.js'; -import {uid2IdSubmodule} from 'modules/uid2IdSystem.js'; -import {euidIdSubmodule} from 'modules/euidIdSystem.js'; -import {admixerIdSubmodule} from 'modules/admixerIdSystem.js'; -import {deepintentDpesSubmodule} from 'modules/deepintentDpesIdSystem.js'; -import {amxIdSubmodule} from '../../../modules/amxIdSystem.js'; -import {kinessoIdSubmodule} from 'modules/kinessoIdSystem.js'; -import {adqueryIdSubmodule} from 'modules/adqueryIdSystem.js'; -import {imuIdSubmodule} from 'modules/imuIdSystem.js'; import * as mockGpt from '../integration/faker/googletag.js'; import 'src/prebid.js'; +import {startAuction} from 'src/prebid'; import {hook} from '../../../src/hook.js'; import {mockGdprConsent} from '../../helpers/consentData.js'; import {getPPID} from '../../../src/adserver.js'; -import {uninstall as uninstallGdprEnforcement} from 'modules/gdprEnforcement.js'; +import {uninstall as uninstallTcfControl} from 'modules/tcfControl.js'; import {allConsent, GDPR_GVLIDS, gdprDataHandler} from '../../../src/consentHandler.js'; import {MODULE_TYPE_UID} from '../../../src/activities/modules.js'; import {ACTIVITY_ENRICH_EIDS} from '../../../src/activities/activities.js'; @@ -95,6 +72,7 @@ describe('User ID', function () { decode(v) { return v; }, + primaryIds: [], aliasName, eids } @@ -161,7 +139,7 @@ describe('User ID', function () { function runBidsHook(...args) { startDelay = delay(); - const result = requestBidsHook(...args, {delay: startDelay}); + const result = startAuctionHook(...args, {delay: startDelay}); return new Promise((resolve) => setTimeout(() => resolve(result))); } @@ -179,7 +157,7 @@ describe('User ID', function () { before(function () { hook.ready(); - uninstallGdprEnforcement(); + uninstallTcfControl(); localStorage.removeItem(PBJS_USER_ID_OPTOUT_NAME); liveIntentIdSubmoduleDoNotFireEvent(); }); @@ -198,8 +176,15 @@ describe('User ID', function () { afterEach(() => { sandbox.restore(); + config.resetConfig(); + startAuction.getHooks({hook: startAuctionHook}).remove(); + startAuction.getHooks({hook: addUserIdsHook}).remove(); }); + after(() => { + init(config); + }) + describe('GVL IDs', () => { beforeEach(() => { sinon.stub(GDPR_GVLIDS, 'register'); @@ -214,6 +199,65 @@ describe('User ID', function () { }) }) + describe('userId config validation', () => { + beforeEach(() => { + sandbox.stub(utils, 'logWarn'); + }); + + function mockConfig(storageConfig = {}) { + return { + name: 'mockModule', + storage: { + name: 'mockStorage', + type: 'cookie', + ...storageConfig + } + } + } + + Object.entries({ + 'not an object': 'garbage', + 'missing name': {}, + 'empty name': {name: ''}, + 'empty storage config': {name: 'mockId', storage: {}}, + 'storage type, but no storage name': mockConfig({name: ''}), + 'storage name, but no storage type': mockConfig({type: undefined}), + }).forEach(([t, config]) => { + it(`should log a warning and reject configuration with ${t}`, () => { + expect(getValidSubmoduleConfigs([config]).length).to.equal(0); + sinon.assert.called(utils.logWarn); + }); + }); + + it('should reject non-array userId configuration', () => { + expect(getValidSubmoduleConfigs({})).to.eql([]); + sinon.assert.called(utils.logWarn); + }); + + it('should accept null configuration', () => { + expect(getValidSubmoduleConfigs()).to.eql([]); + sinon.assert.notCalled(utils.logWarn); + }); + + ['refreshInSeconds', 'expires'].forEach(param => { + describe(`${param} parameter`, () => { + it('should be made a number, when possible', () => { + expect(getValidSubmoduleConfigs([mockConfig({[param]: '123'})])[0].storage[param]).to.equal(123); + }); + + it('should log a warning when not a number', () => { + expect(getValidSubmoduleConfigs([mockConfig({[param]: 'garbage'})])[0].storage[param]).to.not.exist; + sinon.assert.called(utils.logWarn) + }); + + it('should be left untouched when not specified', () => { + expect(getValidSubmoduleConfigs([mockConfig()])[0].storage[param]).to.not.exist; + sinon.assert.notCalled(utils.logWarn); + }); + }) + }) + }) + describe('Decorate Ad Units', function () { beforeEach(function () { // reset mockGpt so nothing else interferes @@ -361,7 +405,7 @@ describe('User ID', function () { customConfig = addConfig(customConfig, 'params', {extend: true}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(customConfig); return expectImmediateBidHook((config) => { @@ -387,7 +431,7 @@ describe('User ID', function () { customConfig = addConfig(customConfig, 'params', {create: false}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(customConfig); return expectImmediateBidHook((config) => { @@ -778,7 +822,7 @@ describe('User ID', function () { it('should set googletag ppid correctly', function () { let adUnits = [getAdUnitMock()]; init(config); - setSubmoduleRegistry([amxIdSubmodule, sharedIdSystemSubmodule, identityLinkSubmodule, imuIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); // before ppid should not be set expect(window.googletag._ppid).to.equal(undefined); @@ -787,10 +831,7 @@ describe('User ID', function () { userSync: { ppid: 'pubcid.org', userIds: [ - { name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }, { name: 'pubCommonId', value: {'pubcid': 'pubCommon-id-value-pubCommon-id-value'} }, - { name: 'identityLink', value: {'idl_env': 'identityLink-id-value-identityLink-id-value'} }, - { name: 'imuid', value: {'imppid': 'imppid-value-imppid-value-imppid-value'} }, ] } }); @@ -942,29 +983,6 @@ describe('User ID', function () { }); }); - it('should set googletag ppid correctly for imuIdSubmodule', function () { - let adUnits = [getAdUnitMock()]; - init(config); - setSubmoduleRegistry([imuIdSubmodule]); - - // before ppid should not be set - expect(window.googletag._ppid).to.equal(undefined); - - config.setConfig({ - userSync: { - ppid: 'ppid.intimatemerger.com', - userIds: [ - { name: 'imuid', value: {'imppid': 'imppid-value-imppid-value-imppid-value'} }, - ] - } - }); - - return expectImmediateBidHook(() => {}, {adUnits}).then(() => { - // ppid should have been set without dashes and stuff - expect(window.googletag._ppid).to.equal('imppidvalueimppidvalueimppidvalue'); - }); - }); - it('should log a warning if PPID too big or small', function () { let adUnits = [getAdUnitMock()]; @@ -1088,24 +1106,26 @@ describe('User ID', function () { }); }); - it('should still resolve promises returned by getUserIdsAsync', () => { - startInit(); - let result = null; - getGlobal().getUserIdsAsync().then((val) => { result = val; }); - return clearStack().then(() => { - expect(result).to.equal(null); // auction has not ended, callback should not have been called - mockIdCallback.callsFake((cb) => cb(MOCK_ID)); - return getGlobal().refreshUserIds().then(clearStack); - }).then(() => { - expect(result).to.deep.equal(getGlobal().getUserIds()) // auction still not over, but refresh was explicitly forced + ['refreshUserIds', 'getUserIdsAsync'].forEach(method => { + it(`should still resolve promises returned by ${method}`, () => { + startInit(); + let result = null; + getGlobal()[method]().then((val) => { result = val; }); + return clearStack().then(() => { + expect(result).to.equal(null); // auction has not ended, callback should not have been called + mockIdCallback.callsFake((cb) => cb(MOCK_ID)); + return getGlobal().refreshUserIds().then(clearStack); + }).then(() => { + expect(result).to.deep.equal(getGlobal().getUserIds()) // auction still not over, but refresh was explicitly forced + }); }); - }); + }) it('should not stop auctions', (done) => { // simulate an infinite `auctionDelay`; refreshing should still allow the auction to continue // as soon as ID submodules have completed init startInit(); - requestBidsHook(() => { + startAuctionHook(() => { done(); }, {adUnits: [getAdUnitMock()]}, {delay: delay()}); getGlobal().refreshUserIds(); @@ -1117,7 +1137,7 @@ describe('User ID', function () { it('should continue the auction when init fails', (done) => { startInit(); - requestBidsHook(() => { + startAuctionHook(() => { done(); }, {adUnits: [getAdUnitMock()]}, @@ -1361,7 +1381,6 @@ describe('User ID', function () { coreStorage.setCookie(PBJS_USER_ID_OPTOUT_NAME, '', EXPIRED_COOKIE_DATE); $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); - config.resetConfig(); }); it('does not fetch ids if opt out cookie exists', function () { @@ -1391,12 +1410,11 @@ describe('User ID', function () { afterEach(function () { $$PREBID_GLOBAL$$.requestBids.removeAll(); utils.logInfo.restore(); - config.resetConfig(); }); it('handles config with no usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({}); // usersync is undefined, and no logInfo message for 'User ID - usersync config updated' expect(typeof utils.logInfo.args[0]).to.equal('undefined'); @@ -1404,14 +1422,14 @@ describe('User ID', function () { it('handles config with empty usersync object', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({userSync: {}}); expect(typeof utils.logInfo.args[0]).to.equal('undefined'); }); it('handles config with usersync and userIds that are empty objs', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{}] @@ -1422,7 +1440,7 @@ describe('User ID', function () { it('handles config with usersync and userIds with empty names or that dont match a submodule.name', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, merkleIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{ @@ -1439,19 +1457,19 @@ describe('User ID', function () { it('config with 1 configurations should create 1 submodules', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); - config.setConfig(getConfigMock(['unifiedId', 'unifiedid', 'cookie'])); + setSubmoduleRegistry([sharedIdSystemSubmodule, pubProvidedIdSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubCommonId', 'cookie'])); 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 () { init(config); - setSubmoduleRegistry([criteoIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { userIds: [{ - name: 'Criteo' + name: 'SharedId' }] } }); @@ -1459,88 +1477,32 @@ describe('User ID', function () { expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 1 submodules'); }); - it('config with 23 configurations should result in 23 submodules add', function () { + it('config with 2 configurations should result in 2 submodules add', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, liveIntentIdSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule, tncidSubModule]); + setSubmoduleRegistry([sharedIdSystemSubmodule, pubProvidedIdSubmodule]); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ - name: 'pubProvidedId' - }, { name: 'pubCommonId', value: {'pubcid': '11111'} }, { - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} - }, { - name: 'id5Id', - storage: {name: 'id5id', type: 'cookie'} - }, { - name: 'identityLink', - storage: {name: 'idl_env', type: 'cookie'} - }, { - name: 'liveIntentId', - storage: {name: '_li_pbid', type: 'cookie'} - }, { - name: 'britepoolId', - value: {'primaryBPID': '279c0161-5152-487f-809e-05d7f7e653fd'} - }, { - name: 'netId', - storage: {name: 'netId', type: 'cookie'} - }, { - name: 'intentIqId', - storage: {name: 'intentIqId', type: 'cookie'} - }, { - name: 'hadronId', - storage: {name: 'hadronId', type: 'html5'} - }, { - name: 'zeotapIdPlus' - }, { - name: 'criteo' - }, { - name: 'mwOpenLinkId' - }, { - name: 'tapadId', - storage: {name: 'tapad_id', type: 'cookie'} - }, { - name: 'uid2' - }, { - name: 'euid' - }, { - name: 'admixerId', - storage: {name: 'admixerId', type: 'cookie'} - }, { - name: 'deepintentId', - storage: {name: 'deepintentId', type: 'cookie'} - }, { - name: 'dmdId', - storage: {name: 'dmdId', type: 'cookie'} - }, { - name: 'amxId', - storage: {name: 'amxId', type: 'html5'} - }, { - name: 'kpuid', - storage: {name: 'kpuid', type: 'cookie'} - }, { - name: 'qid', - storage: {name: 'qid', type: 'html5'} - }, { - name: 'tncId' + name: 'pubProvidedId', + storage: {name: 'pubProvidedId', type: 'cookie'} }] } }); - expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 23 submodules'); + expect(utils.logInfo.args[0][0]).to.exist.and.to.contain('User ID - usersync config updated for 2 submodules'); }); it('config syncDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { syncDelay: 99, userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); @@ -1549,32 +1511,32 @@ describe('User ID', function () { it('config auctionDelay updates module correctly', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: 100, userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); expect(auctionDelay).to.equal(100); }); - it('config auctionDelay defaults to 0 if not a number', function () { + it('config auctionDelay defaults to 500 if not a number', function () { init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, pubProvidedIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: '', userIds: [{ - name: 'unifiedId', - storage: {name: 'unifiedid', type: 'cookie'} + name: 'pubCommonId', + storage: {name: 'pubCommonId', type: 'cookie'} }] } }); - expect(auctionDelay).to.equal(0); + expect(auctionDelay).to.equal(500); }); describe('auction and user sync delays', function () { @@ -1683,7 +1645,7 @@ describe('User ID', function () { 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 + expect(bid.userIdAsEids).to.not.exist;// "mid" is an un-known submodule for USER_IDS_CONFIG in eids.js }); }); @@ -1692,9 +1654,10 @@ describe('User ID', function () { }); }); - it('does not delay auction if not set, delays id fetch after auction ends with syncDelay', function () { + it('does not delay auction if set to 0, delays id fetch after auction ends with syncDelay', function () { config.setConfig({ userSync: { + auctionDelay: 0, syncDelay: 77, userIds: [{ name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} @@ -1702,10 +1665,6 @@ describe('User ID', function () { } }); - // check config has been set correctly - expect(auctionDelay).to.equal(0); - expect(syncDelay).to.equal(77); - return expectImmediateBidHook(auctionSpy, {adUnits}) .then(() => { // should not delay auction @@ -1731,6 +1690,7 @@ describe('User ID', function () { it('does not delay user id sync after auction ends if set to 0', function () { config.setConfig({ userSync: { + auctionDelay: 0, syncDelay: 0, userIds: [{ name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} @@ -1738,8 +1698,6 @@ describe('User ID', function () { } }); - expect(syncDelay).to.equal(0); - return expectImmediateBidHook(auctionSpy, {adUnits}) .then(() => { // auction should not be delayed @@ -1781,13 +1739,46 @@ describe('User ID', function () { }); }); - describe('Request bids hook appends userId to bid objs in adapters', function () { + describe('Start auction hook appends userId to bid objs in adapters', function () { let adUnits; beforeEach(function () { adUnits = [getAdUnitMock()]; }); + it('should include pub-provided eids in userIdAsEids', (done) => { + init(config); + setSubmoduleRegistry([createMockIdSubmodule('mockId', {id: {mockId: 'id'}}, null, {mockId: {source: 'mockid.com', atype: 1}})]); + config.setConfig({ + userSync: { + userIds: [ + {name: 'mockId'} + ] + } + }); + startAuctionHook(({adUnits}) => { + adUnits[0].bids.forEach(bid => { + expect(bid.userIdAsEids.find(eid => eid.source === 'mockid.com')).to.exist; + const bidderEid = bid.userIdAsEids.find(eid => eid.bidder === 'pub-provided'); + expect(bidderEid != null).to.eql(bid.bidder === 'sampleBidder'); + expect(bid.userIdAsEids.find(eid => eid.id === 'pub-provided')).to.exist; + }) + done(); + }, { + adUnits, + ortb2Fragments: { + global: { + user: {ext: {eids: [{id: 'pub-provided'}]}} + }, + bidder: { + sampleBidder: { + user: {ext: {eids: [{bidder: 'pub-provided'}]}} + } + } + } + }) + }) + it('test hook from pubcommonid cookie', function (done) { coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); @@ -1795,7 +1786,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1820,7 +1811,7 @@ describe('User ID', function () { setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'html5'])); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { expect(bid).to.have.deep.nested.property('userId.pubcid'); @@ -1837,1105 +1828,215 @@ describe('User ID', function () { }, {adUnits}); }); - it('test hook from pubcommonid config value object', function (done) { + it('test hook from pubcommonid cookie&html5', function (done) { + const expiration = new Date(Date.now() + 100000).toUTCString(); + coreStorage.setCookie('pubcid', 'testpubcid', expiration); + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', expiration); + init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); - config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); - requestBidsHook(function () { + startAuctionHook(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 + 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); + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); + done(); }, {adUnits}); }); - it('test hook from UnifiedId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('unifiedid_alt', JSON.stringify({'TDID': 'testunifiedid_alt'})); - localStorage.setItem('unifiedid_alt_exp', ''); + it('test hook from pubcommonid cookie&html5, no cookie present', function (done) { + localStorage.setItem('pubcid', 'testpubcid'); + localStorage.setItem('pubcid_exp', new Date(Date.now() + 100000).toUTCString()); init(config); - setSubmoduleRegistry([unifiedIdSubmodule]); - config.setConfig(getConfigMock(['unifiedId', 'unifiedid_alt', 'html5'])); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); - requestBidsHook(function () { + startAuctionHook(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).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'adserver.org', - uids: [{id: 'testunifiedid_alt', atype: 1, ext: {rtiPartner: 'TDID'}}] + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] }); }); }); - localStorage.removeItem('unifiedid_alt'); - localStorage.removeItem('unifiedid_alt_exp'); - done(); - }, {adUnits}); - }); - it('test hook from amxId html5', (done) => { - // simulate existing localStorage values - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', ''); - - init(config); - setSubmoduleRegistry([amxIdSubmodule]); - 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: 'amxdt.net', - uids: [{ - id: 'test_amxid_id', - atype: 1, - }] - }); - }); - }); + localStorage.removeItem('pubcid'); + localStorage.removeItem('pubcid_exp'); - // clear LS - localStorage.removeItem('amxId'); - localStorage.removeItem('amxId_exp'); done(); }, {adUnits}); }); - it('test hook from identityLink html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('idl_env', 'AiGNC8Z5ONyZKSpIPf'); - localStorage.setItem('idl_env_exp', ''); + it('test hook from pubcommonid cookie&html5, no local storage entry', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 100000).toUTCString())); init(config); - setSubmoduleRegistry([identityLinkSubmodule]); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'html5'])); - requestBidsHook(function () { + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie&html5'])); + + startAuctionHook(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).to.have.deep.nested.property('userId.pubcid'); + expect(bid.userId.pubcid).to.equal('testpubcid'); expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveramp.com', - uids: [{id: 'AiGNC8Z5ONyZKSpIPf', atype: 3}] + source: 'pubcid.org', + uids: [{id: 'testpubcid', atype: 1}] }); }); }); - localStorage.removeItem('idl_env'); - localStorage.removeItem('idl_env_exp'); + + coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); + 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 pubcommonid config value object', function (done) { init(config); - setSubmoduleRegistry([identityLinkSubmodule]); - config.setConfig(getConfigMock(['identityLink', 'idl_env', 'cookie'])); + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.setConfig(getConfigValueMock('pubCommonId', {'pubcidvalue': 'testpubcidvalue'})); - requestBidsHook(function () { + startAuctionHook(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}] - }); + expect(bid).to.have.deep.nested.property('userId.pubcidvalue'); + expect(bid.userId.pubcidvalue).to.equal('testpubcidvalue'); + expect(bid.userIdAsEids).to.not.exist; // "pubcidvalue" is an un-known submodule for USER_IDS_CONFIG in eids.js }); }); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook from criteoIdModule cookie', function (done) { - coreStorage.setCookie('storage_bidid', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 100000).toUTCString())); - + it('test hook from pubProvidedId config params', function (done) { init(config); - setSubmoduleRegistry([criteoIdSubmodule]); - config.setConfig(getConfigMock(['criteo', 'storage_bidid', 'cookie'])); + setSubmoduleRegistry([pubProvidedIdSubmodule]); + 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 () { + startAuctionHook(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).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: 'criteo.com', - uids: [{id: 'test_bidid', atype: 1}] + 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' + } + }] }); }); }); - coreStorage.setCookie('storage_bidid', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); - it('test hook from tapadIdModule cookie', function (done) { - coreStorage.setCookie('tapad_id', 'test-tapad-id', (new Date(Date.now() + 100000).toUTCString())); + it('should add new id system ', function (done) { + coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); init(config); - setSubmoduleRegistry([tapadIdSubmodule]); - config.setConfig(getConfigMock(['tapadId', 'tapad_id', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.tapadId'); - expect(bid.userId.tapadId).to.equal('test-tapad-id'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'tapad.com', - uids: [{id: 'test-tapad-id', atype: 1}] - }); - }); - }) - coreStorage.setCookie('tapad_id', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from liveIntentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('_li_pbid', JSON.stringify({'unifiedId': 'random-ls-identifier'})); - localStorage.setItem('_li_pbid_exp', ''); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}] - }); - }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); - - 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())); - - init(config); - setSubmoduleRegistry([kinessoIdSubmodule]); - 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}); - }); - - it('test hook from Kinesso html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('kpuid', 'KINESSO_ID'); - localStorage.setItem('kpuid_exp', ''); - - init(config); - setSubmoduleRegistry([kinessoIdSubmodule]); - config.setConfig(getConfigMock(['kpuid', 'kpuid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.kpuid'); - expect(bid.userId.kpuid).to.equal('KINESSO_ID'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'kpuid.com', - uids: [{id: 'KINESSO_ID', atype: 3}] - }); - }); - }); - localStorage.removeItem('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())); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - 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}] - }); - }); - }); - coreStorage.setCookie('_li_pbid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('eidPermissions fun with bidders', function (done) { - coreStorage.setCookie('pubcid', 'test222', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - 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'); - } - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - getPrebidInternal().setEidPermissions = undefined; - done(); - }, {adUnits}); - }); - - it('eidPermissions fun without bidders', function (done) { - coreStorage.setCookie('pubcid', 'test222', new Date(Date.now() + 5000).toUTCString()); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule]); - let eidPermissions; - getPrebidInternal().setEidPermissions = function (newEidPermissions) { - eidPermissions = newEidPermissions; - } - 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 - }] - }); - }); - }); - getPrebidInternal().setEidPermissions = undefined; - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from pubProvidedId config params', function (done) { - init(config); - setSubmoduleRegistry([pubProvidedIdSubmodule]); - 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.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' - } - }] - }); - }); - }); - 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', ''); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - config.setConfig(getConfigMock(['liveIntentId', '_li_pbid', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.lipb'); - expect(bid.userId.lipb.lipbid).to.equal('random-ls-identifier'); - expect(bid.userId.lipb.segments).to.include('123'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'liveintent.com', - uids: [{id: 'random-ls-identifier', atype: 3}], - ext: {segments: ['123']} - }); - }); - }); - localStorage.removeItem('_li_pbid'); - localStorage.removeItem('_li_pbid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from liveIntentId cookie', function (done) { - coreStorage.setCookie('_li_pbid', JSON.stringify({ - 'unifiedId': 'random-cookie-identifier', - 'segments': ['123'] - }), (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([liveIntentIdSubmodule]); - 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}); - }); - - 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())); - - init(config); - setSubmoduleRegistry([britepoolIdSubmodule]); - 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}] - }); - }); - }); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from dmdId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([dmdIdSubmodule]); - 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}); - }); - - 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())); - - init(config); - setSubmoduleRegistry([netIdSubmodule]); - 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}); - }); - - it('test hook from intentIqId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('intentIqId', 'abcdefghijk', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([intentIqIdSubmodule]); - 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('intentIqId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from hadronId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - - init(config); - setSubmoduleRegistry([hadronIdSubmodule]); - config.setConfig(getConfigMock(['hadronId', 'hadronId', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'audigent.com', - uids: [{id: 'testHadronId1', atype: 1}] - }); - }); - }); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from merkleId cookies - legacy', 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())); - - init(config); - setSubmoduleRegistry([merkleIdSubmodule]); - 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}); - }); - - it('test hook from merkleId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('merkleId', JSON.stringify({ - 'merkleId': [{id: 'testmerkleId', ext: { keyID: 1, ssp: 'ssp1' }}, {id: 'another-random-id-value', ext: { ssp: 'ssp2' }}], - '_svsid': 'svs-id-1' - }), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([merkleIdSubmodule]); - 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.length).to.equal(2); - expect(bid.userIdAsEids.length).to.equal(2); - expect(bid.userIdAsEids[0]).to.deep.equal({ source: 'ssp1.merkleinc.com', uids: [{id: 'testmerkleId', atype: 3, ext: { keyID: 1, ssp: 'ssp1' }}] }); - expect(bid.userIdAsEids[1]).to.deep.equal({ source: 'ssp2.merkleinc.com', uids: [{id: 'another-random-id-value', atype: 3, ext: { ssp: 'ssp2' }}] }); - }); - }); - coreStorage.setCookie('merkleId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from zeotapIdPlus cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('IDP', btoa(JSON.stringify('abcdefghijk')), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([zeotapIdPlusSubmodule]); - 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('IDP', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from mwOpenLinkId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([mwOpenLinkIdSubModule]); - config.setConfig(getConfigMock(['mwOpenLinkId', 'mwol', 'cookie'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.mwOpenLinkId'); - expect(bid.userId.mwOpenLinkId).to.equal('XX-YY-ZZ-123'); - }); - }); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from admixerId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('admixerId', 'testadmixerId'); - localStorage.setItem('admixerId_exp', ''); - - init(config); - setSubmoduleRegistry([admixerIdSubmodule]); - 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}); - }); - - it('test hook from admixerId cookie', function (done) { - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 100000).toUTCString())); - - init(config); - setSubmoduleRegistry([admixerIdSubmodule]); - 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}); - }); - - it('test hook from deepintentId cookies', function (done) { - // simulate existing browser local storage values - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([deepintentDpesSubmodule]); - 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('deepintentId', '', EXPIRED_COOKIE_DATE); - done(); - }, {adUnits}); - }); - - it('test hook from deepintentId html5', function (done) { - // simulate existing browser local storage values - localStorage.setItem('deepintentId', 'testdeepintentId'); - localStorage.setItem('deepintentId_exp', ''); - - init(config); - setSubmoduleRegistry([deepintentDpesSubmodule]); - config.setConfig(getConfigMock(['deepintentId', 'deepintentId', 'html5'])); - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - expect(bid).to.have.deep.nested.property('userId.deepintentId'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'deepintent.com', - uids: [{id: 'testdeepintentId', atype: 3}] - }); - }); - }); - localStorage.removeItem('deepintentId'); - done(); - }, {adUnits}); - }); - - it('test hook from qid html5', (done) => { - // simulate existing localStorage values - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', ''); - - init(config); - setSubmoduleRegistry([adqueryIdSubmodule]); - config.setConfig(getConfigMock(['qid', 'qid', 'html5'])); - - requestBidsHook(() => { - adUnits.forEach((adUnit) => { - adUnit.bids.forEach((bid) => { - expect(bid).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - expect(bid.userIdAsEids[0]).to.deep.equal({ - source: 'adquery.io', - uids: [{ - id: 'testqid', - atype: 1, - }] - }); - }); - }); - - // clear LS - localStorage.removeItem('qid'); - localStorage.removeItem('qid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook when pubCommonId, unifiedId, id5Id, identityLink, britepoolId, intentIqId, zeotapIdPlus, netId, hadronId, Criteo, UID 2.0, admixerId, amxId, dmdId, kpuid, qid and mwOpenLinkId have data to pass', function (done) { - coreStorage.setCookie('pubcid', 'testpubcid', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('unifiedid', JSON.stringify({'TDID': 'testunifiedid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('id5id', JSON.stringify({'universal_uid': 'testid5id'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('idl_env', 'AiGNC8Z5ONyZKSpIPf', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('britepoolid', JSON.stringify({'primaryBPID': 'testbritepoolid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('dmdId', 'testdmdId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('netId', JSON.stringify({'netId': 'testnetId'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('intentIqId', 'testintentIqId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('IDP', btoa(JSON.stringify('zeotapId')), (new Date(Date.now() + 5000).toUTCString())); - // hadronId only supports localStorage - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - coreStorage.setCookie('storage_criteo', JSON.stringify({'criteoId': 'test_bidid'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('mwol', JSON.stringify({eid: 'XX-YY-ZZ-123'}), (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('admixerId', 'testadmixerId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('deepintentId', 'testdeepintentId', (new Date(Date.now() + 5000).toUTCString())); - coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - // amxId only supports localStorage - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', (new Date(Date.now() + 5000)).toUTCString()); - // qid only supports localStorage - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', (new Date(Date.now() + 5000)).toUTCString()); - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, criteoIdSubmodule, mwOpenLinkIdSubModule, tapadIdSubmodule, uid2IdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); - config.setConfig(getConfigMock(['pubCommonId', 'pubcid', 'cookie'], - ['unifiedId', 'unifiedid', 'cookie'], - ['id5Id', 'id5id', 'cookie'], - ['identityLink', 'idl_env', 'cookie'], - ['britepoolId', 'britepoolid', 'cookie'], - ['dmdId', 'dmdId', 'cookie'], - ['netId', 'netId', 'cookie'], - ['intentIqId', 'intentIqId', 'cookie'], - ['zeotapIdPlus', 'IDP', 'cookie'], - ['hadronId', 'hadronId', 'html5'], - ['criteo', 'storage_criteo', 'cookie'], - ['mwOpenLinkId', 'mwol', 'cookie'], - ['tapadId', 'tapad_id', 'cookie'], - ['uid2', 'uid2id', 'cookie'], - ['admixerId', 'admixerId', 'cookie'], - ['amxId', 'amxId', 'html5'], - ['deepintentId', 'deepintentId', 'cookie'], - ['kpuid', 'kpuid', 'cookie'], - ['qid', 'qid', 'html5'])); - - requestBidsHook(function () { - adUnits.forEach(unit => { - unit.bids.forEach(bid => { - // verify that the PubCommonId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // also check that UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('testunifiedid'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdID id was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // also check that netId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that hadronId id was copied to bid - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - // 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).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - - expect(bid.userIdAsEids.length).to.equal(18); - }); - }); - coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('dmdId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - coreStorage.setCookie('storage_criteo', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('mwol', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('uid2id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('admixerId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('deepintentId', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('kpuid', EXPIRED_COOKIE_DATE); - localStorage.removeItem('amxId'); - localStorage.removeItem('amxId_exp'); - localStorage.removeItem('qid'); - localStorage.removeItem('qid_exp'); - done(); - }, {adUnits}); - }); - - it('test hook from UID2 cookie', function (done) { - coreStorage.setCookie('uid2id', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - - init(config); - setSubmoduleRegistry([uid2IdSubmodule]); - 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())); - localStorage.setItem('hadronId', JSON.stringify({'hadronId': 'testHadronId1'})); - localStorage.setItem('hadronId_exp', (new Date(Date.now() + 5000)).toUTCString()); - coreStorage.setCookie('admixerId', 'testadmixerId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('deepintentId', 'testdeepintentId', new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('MOCKID', JSON.stringify({'MOCKID': '123456778'}), new Date(Date.now() + 5000).toUTCString()); - coreStorage.setCookie('__uid2_advertising_token', 'Sample_AD_Token', (new Date(Date.now() + 5000).toUTCString())); - localStorage.setItem('amxId', 'test_amxid_id'); - localStorage.setItem('amxId_exp', new Date(Date.now() + 5000).toUTCString()) - coreStorage.setCookie('kpuid', 'KINESSO_ID', (new Date(Date.now() + 5000).toUTCString())); - localStorage.setItem('qid', 'testqid'); - localStorage.setItem('qid_exp', new Date(Date.now() + 5000).toUTCString()) - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule, id5IdSubmodule, identityLinkSubmodule, britepoolIdSubmodule, netIdSubmodule, intentIqIdSubmodule, zeotapIdPlusSubmodule, hadronIdSubmodule, uid2IdSubmodule, euidIdSubmodule, admixerIdSubmodule, deepintentDpesSubmodule, dmdIdSubmodule, amxIdSubmodule, kinessoIdSubmodule, adqueryIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { syncDelay: 0, userIds: [{ name: 'pubCommonId', storage: {name: 'pubcid', type: 'cookie'} - }, { - name: 'unifiedId', storage: {name: 'unifiedid', type: 'cookie'} - }, { - name: 'id5Id', storage: {name: 'id5id', type: 'cookie'} - }, { - name: 'identityLink', storage: {name: 'idl_env', type: 'cookie'} - }, { - name: 'britepoolId', storage: {name: 'britepoolid', type: 'cookie'} - }, { - name: 'dmdId', storage: {name: 'dmdId', type: 'cookie'} - }, { - name: 'netId', storage: {name: 'netId', type: 'cookie'} - }, { - name: 'intentIqId', storage: {name: 'intentIqId', type: 'cookie'} - }, { - name: 'zeotapIdPlus' - }, { - name: 'hadronId', storage: {name: 'hadronId', type: 'html5'} - }, { - name: 'admixerId', storage: {name: 'admixerId', type: 'cookie'} }, { name: 'mockId', storage: {name: 'MOCKID', type: 'cookie'} - }, { - name: 'uid2' - }, { - name: 'deepintentId', storage: {name: 'deepintentId', type: 'cookie'} - }, { - name: 'amxId', storage: {name: 'amxId', type: 'html5'} - }, { - name: 'kpuid', storage: {name: 'kpuid', type: 'cookie'} - }, { - name: 'qid', storage: {name: 'qid', type: 'html5'} }] } }); @@ -2954,84 +2055,18 @@ describe('User ID', function () { } }); - requestBidsHook(function () { + startAuctionHook(function () { adUnits.forEach(unit => { unit.bids.forEach(bid => { // check PubCommonId id data was copied to bid expect(bid).to.have.deep.nested.property('userId.pubcid'); - expect(bid.userId.pubcid).to.equal('testpubcid'); - // check UnifiedId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.tdid'); - expect(bid.userId.tdid).to.equal('cookie-value-add-module-variations'); - // also check that Id5Id id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.id5id.uid'); - expect(bid.userId.id5id.uid).to.equal('testid5id'); - // also check that identityLink id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.idl_env'); - expect(bid.userId.idl_env).to.equal('AiGNC8Z5ONyZKSpIPf'); - // also check that britepoolId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.britepoolid'); - expect(bid.userId.britepoolid).to.equal('testbritepoolid'); - // also check that dmdId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.dmdId'); - expect(bid.userId.dmdId).to.equal('testdmdId'); - // check MockId data was copied to bid - expect(bid).to.have.deep.nested.property('userId.netId'); - expect(bid.userId.netId).to.equal('testnetId'); // check MockId data was copied to bid expect(bid).to.have.deep.nested.property('userId.mid'); expect(bid.userId.mid).to.equal('1234'); - // also check that intentIqId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.intentIqId'); - expect(bid.userId.intentIqId).to.equal('testintentIqId'); - // also check that zeotapIdPlus id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.IDP'); - expect(bid.userId.IDP).to.equal('zeotapId'); - // also check that hadronId id data was copied to bid - expect(bid).to.have.deep.nested.property('userId.hadronId'); - expect(bid.userId.hadronId).to.equal('testHadronId1'); - 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).to.have.deep.nested.property('userId.qid'); - expect(bid.userId.qid).to.equal('testqid'); - expect(bid.userIdAsEids.length).to.equal(16); }); }); coreStorage.setCookie('pubcid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('unifiedid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('id5id', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('idl_env', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('britepoolid', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('netId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('intentIqId', '', EXPIRED_COOKIE_DATE); - coreStorage.setCookie('IDP', '', EXPIRED_COOKIE_DATE); - localStorage.removeItem('hadronId'); - localStorage.removeItem('hadronId_exp'); - 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); - coreStorage.setCookie('__uid2_advertising_token', '', EXPIRED_COOKIE_DATE); done(); }, {adUnits}); }); @@ -3075,7 +2110,7 @@ describe('User ID', function () { })) } }); - requestBidsHook((req) => { + startAuctionHook((req) => { const activeIds = req.adUnits.flatMap(au => au.bids).flatMap(bid => Object.keys(bid.userId)); expect(Array.from(new Set(activeIds))).to.have.members([MOCK_IDS[1]]); done(); @@ -3086,19 +2121,21 @@ describe('User ID', function () { describe('callbacks at the end of auction', function () { beforeEach(function () { + config.setConfig({ + // callbacks run after auction end only when auctionDelay is 0 + userSync: { + auctionDelay: 0, + } + }) 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 () { 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; }); @@ -3109,115 +2146,55 @@ describe('User ID', function () { } 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'}); init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { + setSubmoduleRegistry([sharedIdSystemSubmodule]); + config.mergeConfig(customCfg); + return runBidsHook({}).then(() => { expect(utils.triggerPixel.called).to.be.false; return endAuction(); }).then(() => { expect(utils.triggerPixel.getCall(0).args[0]).to.include('/any/pubcid/url'); }); }); - - it('unifiedid callback with url', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {url: '/any/unifiedid/url'}); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { - expect(server.requests).to.be.empty; - return endAuction(); - }).then(() => { - expect(server.requests[0].url).to.match(/\/any\/unifiedid\/url/); - }); - }); - - it('unifiedid callback with partner', function () { - let adUnits = [getAdUnitMock()]; - let innerAdUnits; - let customCfg = getConfigMock(['unifiedId', 'unifiedid', 'cookie']); - addConfig(customCfg, 'params', {partner: 'rubicon'}); - - init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, unifiedIdSubmodule]); - config.setConfig(customCfg); - return runBidsHook((config) => { - innerAdUnits = config.adUnits - }, {adUnits}).then(() => { - expect(server.requests).to.be.empty; - return endAuction(); - }).then(() => { - expect(server.requests[0].url).to.equal('https://match.adsrvr.org/track/rid?ttd_pid=rubicon&fmt=json'); - }); - }); }); - describe('Set cookie behavior', function () { - let cookie, cookieStub; - - beforeEach(function () { - setSubmoduleRegistry([sharedIdSystemSubmodule]); - init(config); - cookie = document.cookie; - cookieStub = sinon.stub(document, 'cookie'); - cookieStub.get(() => cookie); - cookieStub.set((val) => cookie = val); - }); - - afterEach(function () { - cookieStub.restore(); - }); - - it('should allow submodules to override the domain', function () { - const submodule = { - submodule: { - domainOverride: function () { - return 'foo.com' - } - }, + describe('Submodule ID storage', () => { + let submodule; + beforeEach(() => { + submodule = { + submodule: {}, config: { name: 'mockId', - storage: { - type: 'cookie' - } }, storageMgr: { - setCookie: sinon.stub() - } + setCookie: sinon.stub(), + setDataInLocalStorage: sinon.stub() + }, + enabledStorageTypes: ['cookie', 'html5'] } - setStoredValue(submodule, 'bar'); - expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); }); - it('should pass no domain if submodule does not override the domain', function () { - const submodule = { - submodule: {}, - config: { - name: 'mockId', - storage: { - type: 'cookie' - } - }, - storageMgr: { - setCookie: sinon.stub() + describe('Set cookie behavior', function () { + beforeEach(() => { + submodule.config.storage = { + type: 'cookie' } - } - setStoredValue(submodule, 'bar'); - expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); + }); + it('should allow submodules to override the domain', function () { + submodule.submodule.domainOverride = function() { + return 'foo.com' + } + setStoredValue(submodule, 'bar'); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal('foo.com'); + }); + + it('should pass no domain if submodule does not override the domain', function () { + setStoredValue(submodule, 'bar'); + expect(submodule.storageMgr.setCookie.getCall(0).args[4]).to.equal(null); + }); }); }); @@ -3361,6 +2338,20 @@ describe('User ID', function () { sinon.assert.calledOnce(mockExtendId); }); }); + + it('calls getId with the list of enabled storage types', function() { + setStorage({lastDelta: 1000}); + config.setConfig(userIdConfig); + + let innerAdUnits; + return runBidsHook((config) => { + innerAdUnits = config.adUnits + }, {adUnits}).then(() => { + sinon.assert.calledOnce(mockGetId); + + expect(mockGetId.getCall(0).args[0].enabledStorageTypes).to.deep.equal([ userIdConfig.userSync.userIds[0].storage.type ]); + }); + }); }); describe('requestDataDeletion', () => { @@ -3376,21 +2367,23 @@ describe('User ID', function () { onDataDeletionRequest: sinon.stub() } } - let mod1, mod2, mod3, cfg1, cfg2, cfg3; + let mod1, mod2, mod3, mod4, cfg1, cfg2, cfg3, cfg4; beforeEach(() => { init(config); mod1 = idMod('id1', 'val1'); mod2 = idMod('id2', 'val2'); mod3 = idMod('id3', 'val3'); + mod4 = idMod('id4', 'val4'); cfg1 = getStorageMock('id1', 'id1', 'cookie'); cfg2 = getStorageMock('id2', 'id2', 'html5'); - cfg3 = {name: 'id3', value: {id3: 'val3'}}; - setSubmoduleRegistry([mod1, mod2, mod3]); + cfg3 = getStorageMock('id3', 'id3', 'cookie&html5'); + cfg4 = {name: 'id4', value: {id4: 'val4'}}; + setSubmoduleRegistry([mod1, mod2, mod3, mod4]); config.setConfig({ auctionDelay: 1, userSync: { - userIds: [cfg1, cfg2, cfg3] + userIds: [cfg1, cfg2, cfg3, cfg4] } }); return getGlobal().refreshUserIds(); @@ -3399,16 +2392,21 @@ describe('User ID', function () { it('deletes stored IDs', () => { expect(coreStorage.getCookie('id1')).to.exist; expect(coreStorage.getDataFromLocalStorage('id2')).to.exist; + expect(coreStorage.getCookie('id3')).to.exist; + expect(coreStorage.getDataFromLocalStorage('id3')).to.exist; requestDataDeletion(sinon.stub()); expect(coreStorage.getCookie('id1')).to.not.exist; expect(coreStorage.getDataFromLocalStorage('id2')).to.not.exist; + expect(coreStorage.getCookie('id3')).to.not.exist; + expect(coreStorage.getDataFromLocalStorage('id3')).to.not.exist; }); it('invokes onDataDeletionRequest', () => { requestDataDeletion(sinon.stub()); sinon.assert.calledWith(mod1.onDataDeletionRequest, cfg1, {id1: 'val1'}); - sinon.assert.calledWith(mod2.onDataDeletionRequest, cfg2, {id2: 'val2'}) - sinon.assert.calledWith(mod3.onDataDeletionRequest, cfg3, {id3: 'val3'}) + sinon.assert.calledWith(mod2.onDataDeletionRequest, cfg2, {id2: 'val2'}); + sinon.assert.calledWith(mod3.onDataDeletionRequest, cfg3, {id3: 'val3'}); + sinon.assert.calledWith(mod4.onDataDeletionRequest, cfg4, {id4: 'val4'}); }); describe('does not choke when onDataDeletionRequest', () => { @@ -3423,11 +2421,64 @@ describe('User ID', function () { requestDataDeletion(next, arg); sinon.assert.calledOnce(mod2.onDataDeletionRequest); sinon.assert.calledOnce(mod3.onDataDeletionRequest); + sinon.assert.calledOnce(mod4.onDataDeletionRequest); sinon.assert.calledWith(next, arg); }) }) }) }); + + describe('submodules not added', () => { + const eid = { + source: 'example.com', + uids: [{id: '1234', atype: 3}] + }; + let adUnits; + let startAuctionStub; + function saHook(fn, ...args) { + return startAuctionStub(...args); + } + beforeEach(() => { + adUnits = [{code: 'au1', bids: [{bidder: 'sampleBidder'}]}]; + startAuctionStub = sinon.stub(); + startAuction.before(saHook); + config.resetConfig(); + }); + afterEach(() => { + startAuction.getHooks({hook: saHook}).remove(); + }) + + it('addUserIdsHook', function (done) { + addUserIdsHook(function () { + adUnits.forEach(unit => { + unit.bids.forEach(bid => { + expect(bid).to.have.deep.nested.property('userIdAsEids.0.source'); + expect(bid).to.have.deep.nested.property('userIdAsEids.0.uids.0.id'); + expect(bid.userIdAsEids[0].source).to.equal('example.com'); + expect(bid.userIdAsEids[0].uids[0].id).to.equal('1234'); + }); + }); + done(); + }, { + adUnits, + ortb2Fragments: { + global: {user: {ext: {eids: [eid]}}}, + bidder: {} + } + }); + }); + + it('should add userIdAsEids and merge ortb2.user.ext.eids even if no User ID submodules', () => { + init(config); + config.setConfig({ + ortb2: {user: {ext: {eids: [eid]}}} + }) + expect(startAuction.getHooks({hook: startAuctionHook}).length).equal(0); + expect(startAuction.getHooks({hook: addUserIdsHook}).length).equal(1); + $$PREBID_GLOBAL$$.requestBids({adUnits}); + sinon.assert.calledWith(startAuctionStub, sinon.match.hasNested('adUnits[0].bids[0].userIdAsEids[0]', eid)); + }); + }); }); describe('handles config with ESP configuration in user sync object', function() { @@ -3444,7 +2495,7 @@ describe('User ID', function () { expect(typeof (getGlobal()).getEncryptedEidsForSource).to.equal('function'); }); - it('pbjs.getEncryptedEidsForSource should return the string without encryption if encryption is false', (done) => { + it('pbjs.getEncryptedEidsForSource should return the string without encryption if encryption is false', () => { init(config); setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ @@ -3466,43 +2517,42 @@ describe('User ID', function () { }, }); const encrypt = false; - (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((data) => { + return (getGlobal()).getEncryptedEidsForSource(signalSources[0], encrypt).then((data) => { let users = (getGlobal()).getUserIdsAsEids(); expect(data).to.equal(users[0].uids[0].id); - done(); - }).catch(done); + }) }); it('pbjs.getEncryptedEidsForSource should return prioritized id as non-encrypted string', (done) => { init(config); - setSubmoduleRegistry([ - createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}), - createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}), - createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, { - uid2: { - source: 'uidapi.com', - getValue(data) { - return data.id - } - }, - pubcid: { - source: 'pubcid.org', - }, - lipb: { - source: 'liveintent.com', - getValue(data) { - return data.lipbid - } + const EIDS = { + uid2: { + source: 'uidapi.com', + getValue(data) { + return data.id } - }), - createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, { - merkleId: { - source: 'merkleinc.com', - getValue(data) { - return data.id - } + }, + pubcid: { + source: 'pubcid.org', + }, + lipb: { + source: 'liveintent.com', + getValue(data) { + return data.lipbid } - }) + }, + merkleId: { + source: 'merkleinc.com', + getValue(data) { + return data.id + } + } + } + setSubmoduleRegistry([ + createMockIdSubmodule('mockId1Module', {id: {uid2: {id: 'uid2_value'}}}, null, EIDS), + createMockIdSubmodule('mockId2Module', {id: {pubcid: 'pubcid_value', lipb: {lipbid: 'lipbid_value_from_mockId2Module'}}}, null, EIDS), + createMockIdSubmodule('mockId3Module', {id: {uid2: {id: 'uid2_value_from_mockId3Module'}, pubcid: 'pubcid_value_from_mockId3Module', lipb: {lipbid: 'lipbid_value'}, merkleId: {id: 'merkleId_value_from_mockId3Module'}}}, null, EIDS), + createMockIdSubmodule('mockId4Module', {id: {merkleId: {id: 'merkleId_value'}}}, null, EIDS) ]); config.setConfig({ userSync: { @@ -3586,14 +2636,12 @@ describe('User ID', function () { ] } init(config); - setSubmoduleRegistry([sharedIdSystemSubmodule, amxIdSubmodule]); + setSubmoduleRegistry([sharedIdSystemSubmodule]); config.setConfig({ userSync: { auctionDelay: 10, userIds: [{ name: 'pubCommonId', value: {'pubcid': '11111'} - }, { - name: 'amxId', value: {'amxId': 'amx-id-value-amx-id-value-amx-id-value'} }] } }); @@ -3649,4 +2697,246 @@ describe('User ID', function () { }); }) }); + + describe('enrichEids', () => { + let idValues; + + function mockIdSubmodule(key, ...extraKeys) { + return { + name: `${key}Module`, + decode(v) { return v }, + getId() { + return { + id: Object.fromEntries( + idValues[key]?.map(mod => [mod, key === mod ? `${key}_value` : `${mod}_value_from_${key}Module`]) + ) + } + }, + primaryIds: [key], + eids: { + [key]: { + source: `${key}.com`, + atype: 1, + }, + ...Object.fromEntries(extraKeys.map(extraKey => [extraKey, { + source: `${extraKey}.com`, + atype: 1, + getUidExt() { + return {provider: `${key}Module`} + } + }])) + } + } + } + + beforeEach(() => { + idValues = { + mockId1: ['mockId1'], + mockId2: ['mockId2', 'mockId3'], + mockId3: ['mockId1', 'mockId2', 'mockId3', 'mockId4'], + mockId4: ['mockId4'] + } + init(config); + + setSubmoduleRegistry([ + mockIdSubmodule('mockId1'), + mockIdSubmodule('mockId2', 'mockId3'), + mockIdSubmodule('mockId3', 'mockId1', 'mockId2', 'mockId4'), + mockIdSubmodule('mockId4') + ]); + }) + + function enrich({global = {}, bidder = {}} = {}) { + return getGlobal().getUserIdsAsync().then(() => { + enrichEids({global, bidder}); + return {global, bidder}; + }) + } + + function eidsFrom(nameFromModuleMapping) { + return Object.entries(nameFromModuleMapping).map(([key, module]) => { + const owner = `${key}Module` === module + const uid = { + id: owner ? `${key}_value` : `${key}_value_from_${module}`, + atype: 1, + }; + if (!owner) { + uid.ext = {provider: module} + } + return { + source: `${key}.com`, + uids: [uid] + } + }) + } + + function bidderEids(bidderMappings) { + return Object.fromEntries( + Object.entries(bidderMappings).map(([bidder, mapping]) => [bidder, {user: {ext: {eids: eidsFrom(mapping)}}}]) + ) + } + + it('should use lower-priority module if higher priority module cannot provide an id', () => { + idValues.mockId3 = [] + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId3Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })) + }); + }); + + it('should not choke if no id is available for a module', () => { + idValues.mockId1 = [] + config.setConfig({ + userSync: { + userIds: [ + { name: 'mockId1Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user?.ext?.eids).to.not.exist; + }); + }); + + it('should add EIDs that are not bidder-restricted', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId3Module', 'mockId1Module'], + mockId4: ['mockId4Module', 'mockId3Module'] + }, + userIds: [ + { name: 'mockId1Module' }, + { name: 'mockId2Module' }, + { name: 'mockId3Module' }, + { name: 'mockId4Module' }, + ] + } + }); + return enrich().then(({global}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId3Module', + mockId2: 'mockId2Module', + mockId3: 'mockId3Module', + mockId4: 'mockId4Module' + })); + }); + }); + + it('should separate bidder-restricted eids', () => { + config.setConfig({ + userSync: { + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA', 'bidderB'] }, + { name: 'mockId4Module' }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId4: 'mockId4Module' + })); + [bidder.bidderA, bidder.bidderB].forEach(bidderCfg => { + expect(bidderCfg.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })) + }) + }); + }) + + it('should exclude bidder-unrestricted IDs that conflict for some bidders', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId1Module', 'mockId3Module'], + }, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA'] }, + { name: 'mockId3Module' }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(eidsFrom({ + mockId2: 'mockId3Module', + mockId3: 'mockId3Module', + mockId4: 'mockId3Module' + })); + expect(bidder).to.eql({}); + }); + }); + + it('should provide bidder-specific IDs, even when they conflict across bidders', () => { + config.setConfig({ + userSync: { + idPriority: { + mockId1: ['mockId1Module', 'mockId3Module'], + }, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA'] }, + { name: 'mockId3Module', bidders: ['bidderB'] }, + ] + } + }); + return enrich().then(({global, bidder}) => { + expect(global.user?.ext?.eids).to.not.exist; + expect(bidder).to.eql(bidderEids({ + bidderA: { + mockId1: 'mockId1Module' + }, + bidderB: { + mockId1: 'mockId1Module', + mockId2: 'mockId3Module', + mockId3: 'mockId3Module', + mockId4: 'mockId3Module' + } + })); + }); + }); + + it('should not override pub-provided EIDS', () => { + config.setConfig({ + userSync: { + auctionDelay: 10, + userIds: [ + { name: 'mockId1Module', bidders: ['bidderA', 'bidderB'] }, + { name: 'mockId4Module' }, + ] + } + }); + const globalEids = [{pub: 'provided'}]; + const bidderAEids = [{bidder: 'A'}] + const fpd = { + global: {user: {ext: {eids: globalEids}}}, + bidder: { + bidderA: { + user: {ext: {eids: bidderAEids}} + } + } + } + return enrich(fpd).then(({global, bidder}) => { + expect(global.user.ext.eids).to.eql(globalEids.concat(eidsFrom({ + mockId4: 'mockId4Module' + }))); + expect(bidder.bidderA.user.ext.eids).to.eql(bidderAEids.concat(eidsFrom({ + mockId1: 'mockId1Module' + }))); + expect(bidder.bidderB.user.ext.eids).to.eql(eidsFrom({ + mockId1: 'mockId1Module' + })); + }); + }) + }); }); diff --git a/test/spec/modules/utiqSystem_spec.js b/test/spec/modules/utiqIdSystem_spec.js similarity index 72% rename from test/spec/modules/utiqSystem_spec.js rename to test/spec/modules/utiqIdSystem_spec.js index afeeea7c3ea..ef8e4efc5c5 100644 --- a/test/spec/modules/utiqSystem_spec.js +++ b/test/spec/modules/utiqIdSystem_spec.js @@ -1,9 +1,10 @@ import { expect } from 'chai'; -import { utiqSubmodule } from 'modules/utiqSystem.js'; -import { storage } from 'modules/utiqSystem.js'; +import { utiqIdSubmodule } from 'modules/utiqIdSystem.js'; +import { storage } from 'modules/utiqIdSystem.js'; -describe('utiqSystem', () => { +describe('utiqIdSystem', () => { const utiqPassKey = 'utiqPass'; + const netIdKey = 'netid_utiq_adtechpass'; const getStorageData = (idGraph) => { if (!idGraph) { @@ -17,7 +18,7 @@ describe('utiqSystem', () => { }; it('should have the correct module name declared', () => { - expect(utiqSubmodule.name).to.equal('utiq'); + expect(utiqIdSubmodule.name).to.equal('utiqId'); }); describe('utiq getId()', () => { @@ -26,13 +27,13 @@ describe('utiqSystem', () => { }); it('it should return object with key callback', () => { - expect(utiqSubmodule.getId()).to.have.property('callback'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); }); it('should return object with key callback with value type - function', () => { storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData())); - expect(utiqSubmodule.getId()).to.have.property('callback'); - expect(typeof utiqSubmodule.getId().callback).to.be.equal('function'); + expect(utiqIdSubmodule.getId()).to.have.property('callback'); + expect(typeof utiqIdSubmodule.getId().callback).to.be.equal('function'); }); it('tests if localstorage & JSON works properly ', () => { @@ -50,7 +51,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData(idGraph))); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); @@ -61,7 +62,7 @@ describe('utiqSystem', () => { 'domain': 'test.domain', 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -82,7 +83,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -105,7 +106,7 @@ describe('utiqSystem', () => { 'atid': 'atidValue', }; - const response = utiqSubmodule.getId({params: {maxDelayTime: 200}}); + const response = utiqIdSubmodule.getId({params: {maxDelayTime: 200}}); expect(response).to.have.property('callback'); expect(response.callback.toString()).contain('result(callback)'); @@ -138,7 +139,7 @@ describe('utiqSystem', () => { ]; VALID_API_RESPONSES.forEach(responseData => { it('should return a newly constructed object with the utiq for a payload with {utiq: value}', () => { - expect(utiqSubmodule.decode(responseData.payload)).to.deep.equal( + expect(utiqIdSubmodule.decode(responseData.payload)).to.deep.equal( {utiq: responseData.expected} ); }); @@ -146,7 +147,7 @@ describe('utiqSystem', () => { [{}, '', {foo: 'bar'}].forEach((response) => { it(`should return null for an invalid response "${JSON.stringify(response)}"`, () => { - expect(utiqSubmodule.decode(response)).to.be.null; + expect(utiqIdSubmodule.decode(response)).to.be.null; }); }); }); @@ -177,7 +178,7 @@ describe('utiqSystem', () => { window.dispatchEvent(new MessageEvent('message', eventData)); - const response = utiqSubmodule.getId(); + const response = utiqIdSubmodule.getId(); expect(response).to.have.property('id'); expect(response.id).to.have.property('utiq'); expect(response.id.utiq).to.be.equal('atidValue'); @@ -185,4 +186,42 @@ describe('utiqSystem', () => { }); }); }); + + describe('utiq getUtiqFromStorage', () => { + afterEach(() => { + storage.removeDataFromLocalStorage(utiqPassKey); + }); + + it(`correctly set utiqPassKey as adtechpass utiq value for ${netIdKey} empty`, (done) => { + // given + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData({ + 'domain': 'TEST DOMAIN', + 'atid': 'TEST ATID', + }))); // setting idGraph + storage.setDataInLocalStorage(netIdKey, ''); // setting an empty value + + // when + const response = utiqIdSubmodule.getId(); + + // then + expect(response.id.utiq).to.be.equal('TEST ATID'); + done(); + }); + + it(`correctly set netIdAdtechpass as adtechpass utiq value for ${netIdKey} settled`, (done) => { + // given + storage.setDataInLocalStorage(utiqPassKey, JSON.stringify(getStorageData({ + 'domain': 'TEST DOMAIN', + 'atid': 'TEST ATID', + }))); // setting idGraph + storage.setDataInLocalStorage(netIdKey, 'testNetIdValue'); // setting a correct value + + // when + const response = utiqIdSubmodule.getId(); + + // then + expect(response.id.utiq).to.be.equal('testNetIdValue'); + done(); + }); + }); }); diff --git a/test/spec/modules/vdoaiBidAdapter_spec.js b/test/spec/modules/vdoaiBidAdapter_spec.js index b1cfa606d84..e7f153fa237 100644 --- a/test/spec/modules/vdoaiBidAdapter_spec.js +++ b/test/spec/modules/vdoaiBidAdapter_spec.js @@ -1,11 +1,19 @@ import {assert, expect} from 'chai'; import {spec} from 'modules/vdoaiBidAdapter.js'; import {newBidder} from 'src/adapters/bidderFactory.js'; +import { config } from 'src/config.js'; -const ENDPOINT_URL = 'https://prebid.vdo.ai/auction'; +const ENDPOINT_URL = 'https://prebid-v2.vdo.ai/auction'; describe('vdoaiBidAdapter', function () { const adapter = newBidder(spec); + const sandbox = sinon.sandbox.create(); + beforeEach(function() { + sandbox.stub(config, 'getConfig').withArgs('coppa').returns(true); + }); + afterEach(function() { + sandbox.restore(); + }); describe('isBidRequestValid', function () { let bid = { 'bidder': 'vdoai', @@ -20,16 +28,33 @@ describe('vdoaiBidAdapter', function () { 'bidderRequestId': '1234asdf1234asdf', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf120' }; + let invalidParams = { + 'bidder': 'vdoai', + 'params': { + placementId: false + }, + '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); }); + it('should return false where required params not found', function () { + expect(spec.isBidRequestValid(invalidParams)).to.equal(false); + }); }); describe('buildRequests', function () { let bidRequests = [ { 'bidder': 'vdoai', 'params': { - placementId: 'testPlacementId' + placementId: 'testPlacementId', + bidFloor: 0.1 }, 'sizes': [ [300, 250] @@ -37,16 +62,84 @@ describe('vdoaiBidAdapter', function () { 'bidId': '23beaa6af6cdde', 'bidderRequestId': '19c0c1efdf37e7', 'auctionId': '61466567-d482-4a16-96f0-fe5f25ffbdf1', - 'mediaTypes': 'banner' + 'mediaType': 'banner', + 'adUnitCode': '1234', + 'mediaTypes': { + banner: { + sizes: [300, 250] + } + } + }, + { + 'bidder': 'vdoai', + 'params': { + placementId: 'testPlacementId', + bidFloor: 0.1 + }, + 'width': '300', + 'height': '200', + 'bidId': 'bidId123', + 'referer': 'www.example.com', + 'mediaType': 'video', + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [[640, 360]] + } + } } ]; let bidderRequests = { + timeout: 3000, 'refererInfo': { 'numIframes': 0, 'reachedTop': true, 'referer': 'https://example.com', - 'stack': ['https://example.com'] + 'stack': ['https://example.com'], + 'page': 'example.com', + 'ref': 'example2.com' + }, + 'ortb2': { + source: { + tid: 123456789 + }, + 'site': { + 'domain': 'abc.com', + 'publisher': { + 'domain': 'abc.com' + } + } + }, + 'ortb2Imp': { + ext: { + tid: '12345', + gpid: '1234' + } + }, + gdprConsent: { + consentString: 'abc', + gdprApplies: true, + addtlConsent: 'xyz' + }, + gppConsent: { + gppString: 'abcd', + applicableSections: '' + }, + uspConsent: { + uspConsent: '12345' + }, + userIdAsEids: {}, + schain: { + 'ver': '1.0', + 'complete': 1, + 'nodes': [ + { + 'asi': 'vdo.ai', + 'sid': '4359', + 'hp': 1 + } + ] } }; @@ -57,6 +150,44 @@ describe('vdoaiBidAdapter', function () { it('attaches source and version to endpoint URL as query params', function () { expect(request[0].url).to.equal(ENDPOINT_URL); }); + it('should contain all keys', function() { + expect(request[0].data.pageInfo).to.include.all.keys('location', 'referrer', 'stack', 'numIframes', 'sHeight', 'sWidth', 'docHeight', 'wHeight', 'wWidth', 'oHeight', 'oWidth', 'aWidth', 'aHeight', 'sLeft', 'sTop', 'hLength', 'xOffset', 'yOffset', 'version'); + }) + it('should return empty array if no valid bid was passed', function () { + expect(spec.buildRequests([], bidderRequests)).to.be.empty; + }); + it('should not send invalid schain', function () { + delete bidderRequests.schain.nodes[0].asi; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.schain).to.be.undefined; + }); + it('should not send invalid schain', function () { + delete bidderRequests.schain; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.schain).to.be.undefined; + }); + it('should check for correct sizes', function () { + delete bidRequests[1].mediaTypes.video.playerSize; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[1].data.playerSize).to.be.empty; + }); + it('should not pass undefined in GDPR string', function () { + delete bidderRequests.gdprConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.gdprConsent).to.be.undefined; + }); + + it('should not pass undefined in gppConsent', function () { + delete bidderRequests.gppConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.gppConsent).to.be.undefined; + }); + + it('should not pass undefined in uspConsent', function () { + delete bidderRequests.uspConsent; + let result = spec.buildRequests(bidRequests, bidderRequests); + expect(result[0].data.uspConsent).to.be.undefined; + }); }); describe('interpretResponse', function () { @@ -75,29 +206,31 @@ describe('vdoaiBidAdapter', function () { } ]; 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'] + body: + { + 'price': 2, + 'adid': 'test-ad', + 'adomain': [ + 'text.abc' + ], + 'w': 300, + 'h': 250, + 'vdoCreative': '

I am an ad

', + 'bidId': '31d1375caab87a', + 'mediaType': 'banner' } }; + it('should get the correct bid response', function () { let expectedResponse = [{ - 'requestId': 'bidId123', - 'cpm': 4.2, + 'requestId': '31d1375caab87a', + 'cpm': 2, 'width': 300, 'height': 250, - 'creativeId': '12345asdfg', - 'currency': 'EUR', + 'creativeId': 'test-ad', + 'currency': 'USD', 'netRevenue': true, - 'ttl': 3000, + 'ttl': 60, 'ad': '

I am an ad

', 'meta': { 'advertiserDomains': ['text.abc'] @@ -112,9 +245,9 @@ describe('vdoaiBidAdapter', function () { let serverResponse = { body: { 'vdoCreative': '', - 'price': 4.2, + 'price': 2, 'adid': '12345asdfg', - 'currency': 'EUR', + 'currency': 'USD', 'statusMessage': 'Bid available', 'requestId': 'bidId123', 'width': 300, @@ -133,7 +266,13 @@ describe('vdoaiBidAdapter', function () { 'height': '200', 'bidId': 'bidId123', 'referer': 'www.example.com', - 'mediaType': 'video' + 'mediaType': 'video', + 'mediaTypes': { + video: { + context: 'instream', + playerSize: [[640, 360]] + } + } } } ]; @@ -142,5 +281,131 @@ describe('vdoaiBidAdapter', function () { expect(result[0]).to.have.property('vastXml'); expect(result[0]).to.have.property('mediaType', 'video'); }); + it('should not return invalid responses', function() { + serverResponse.body.w = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid height', function() { + serverResponse.body.w = 300; + serverResponse.body.h = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid cpm', function() { + serverResponse.body.h = 250; + serverResponse.body.price = 0; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + it('should not return invalid responses with invalid creative ID', function() { + serverResponse.body.price = 2; + serverResponse.body.adid = undefined; + let result = spec.interpretResponse(serverResponse, bidRequest[0]); + expect(result).to.be.empty; + }); + }); + describe('getUserSyncs', function() { + it('should return correct sync urls', function() { + let serverResponse = [{ + body: { + 'vdoCreative': '', + 'price': 2, + 'adid': '12345asdfg', + 'currency': 'USD', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video', + 'cookiesync': { + 'status': 'no_cookie', + 'bidder_status': [ + { + 'bidder': 'vdoai', + 'no_cookie': true, + 'usersync': { + 'url': 'https://rtb.vdo.ai/setuid/', + 'type': 'iframe' + } + } + ] + } + } + }]; + + let syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0].url).to.be.equal(serverResponse[0].body.cookiesync.bidder_status[0].usersync.url); + + syncUrls = spec.getUserSyncs({ + iframeEnabled: false + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + }); + it('should not return invalid sync urls', function() { + let serverResponse = [{ + body: { + 'vdoCreative': '', + 'price': 2, + 'adid': '12345asdfg', + 'currency': 'USD', + 'statusMessage': 'Bid available', + 'requestId': 'bidId123', + 'width': 300, + 'height': 250, + 'netRevenue': true, + 'mediaType': 'video', + 'cookiesync': { + 'status': 'no_cookie', + 'bidder_status': [ + ] + } + } + }]; + + var syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body.cookiesync.bidder_status; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body.cookiesync; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + + delete serverResponse[0].body; + syncUrls = spec.getUserSyncs({ + iframeEnabled: true + }, serverResponse); + expect(syncUrls[0]).to.be.undefined; + }); + }); + + describe('onTimeout', function() { + it('should run without errors', function() { + spec.onTimeout(); + }); + }); + + describe('onBidWon', function() { + it('should run without errors', function() { + spec.onBidWon(); + }); + }); + + describe('onSetTargeting', function() { + it('should run without errors', function() { + spec.onSetTargeting(); + }); }); }); diff --git a/test/spec/modules/viantOrtbBidAdapter_spec.js b/test/spec/modules/viantOrtbBidAdapter_spec.js index 73fdb7f3dc8..67ae8b07821 100644 --- a/test/spec/modules/viantOrtbBidAdapter_spec.js +++ b/test/spec/modules/viantOrtbBidAdapter_spec.js @@ -1,8 +1,9 @@ -import { spec, converter } from 'modules/viantOrtbBidAdapter.js'; +import {spec, converter} from 'modules/viantOrtbBidAdapter.js'; import {assert, expect} from 'chai'; -import { deepClone } from '../../../src/utils'; +import {deepClone} from '../../../src/utils'; import {buildWindowTree} from '../../helpers/refererDetectionHelper'; import {detectReferer} from '../../../src/refererDetection'; + describe('viantOrtbBidAdapter', function () { function testBuildRequests(bidRequests, bidderRequestBase) { let clonedBidderRequest = deepClone(bidderRequestBase); @@ -10,7 +11,8 @@ describe('viantOrtbBidAdapter', function () { let requests = spec.buildRequests(bidRequests, clonedBidderRequest); return requests } - describe('isBidRequestValid', function() { + + describe('isBidRequestValid', function () { function makeBid() { return { 'bidder': 'viant', @@ -46,9 +48,7 @@ describe('viantOrtbBidAdapter', function () { it('should return true if placementId is not passed ', function () { let bid = makeBid(); delete bid.params.placementId; - bid.ortb2Imp = { - - } + bid.ortb2Imp = {} expect(spec.isBidRequestValid(bid)).to.equal(true); }); @@ -98,6 +98,7 @@ describe('viantOrtbBidAdapter', function () { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' } } + it('should return true when required params found', function () { expect(spec.isBidRequestValid(makeBid())).to.equal(true); }); @@ -141,6 +142,7 @@ describe('viantOrtbBidAdapter', function () { 'transactionId': '4008d88a-8137-410b-aa35-fbfdabcb478e' } } + it('should return true when required params found', function () { expect(spec.isBidRequestValid(makeBid())).to.equal(true); }); @@ -180,6 +182,57 @@ describe('viantOrtbBidAdapter', function () { 'src': 'client', 'bidRequestsCount': 1 }]; + const basePMPDealsBidRequests = [{ + 'bidder': 'viant', + 'params': { + 'publisherId': '464', + 'placementId': '1' + }, + 'ortb2Imp': { + 'pmp': { + 'private_auction': 0, + 'deals': [ + { + 'id': '1234567', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 1, + 'private_auction': 1 + } + }, + { + 'id': '1234568', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 0 + } + } + ] + }, + }, + 'mediaTypes': { + 'banner': { + 'sizes': [[728, 90]] + } + }, + 'gdprConsent': { + 'consentString': 'consentString', + 'gdprApplies': true, + }, + 'uspConsent': '1YYY', + 'sizes': [[728, 90]], + 'transactionId': '1111474f-58b1-4368-b812-84f8c937a099', + 'adUnitCode': 'div-gpt-ad-1460505748561-0', + 'bidId': '243310435309b5', + 'bidderRequestId': '18084284054531', + 'auctionId': 'e7b34fa3-8654-424e-8c49-03e509e53d8c', + 'src': 'client', + 'bidRequestsCount': 1 + }]; const testWindow = buildWindowTree(['https://www.example.com/test', 'https://www.example.com/other/page', 'https://www.example.com/third/page'], 'https://othersite.com/', 'https://example.com/canonical/page'); const baseBidderRequestReferer = detectReferer(testWindow)(); @@ -215,7 +268,7 @@ describe('viantOrtbBidAdapter', function () { }); it('sends bid requests to the correct endpoint', function () { const url = testBuildRequests(baseBannerBidRequests, baseBidderRequest)[0].url; - expect(url).to.equal('https://bidders-us-east-1.adelphic.net/d/rtb/v25/prebid/bidder'); + expect(url).to.equal('https://bidders-us.adelphic.net/d/rtb/v25/prebid/bidder'); }); it('sends site', function () { @@ -236,6 +289,34 @@ describe('viantOrtbBidAdapter', function () { const requestBody = testBuildRequests(clonedBannerRequests, baseBidderRequest)[0].data; expect(requestBody.imp[0].banner.pos).to.equal(1); }); + it('includes the deals in the bid request', function () { + const requestBody = testBuildRequests(basePMPDealsBidRequests, baseBidderRequest)[0].data; + expect(requestBody.imp[0].pmp).to.be.not.null; + expect(requestBody.imp[0].pmp).to.deep.equal({ + 'private_auction': 0, + 'deals': [ + { + 'id': '1234567', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 1, + 'private_auction': 1 + } + }, + { + 'id': '1234568', + 'at': 3, + 'bidfloor': 25, + 'bidfloorcur': 'USD', + 'ext': { + 'must_bid': 0 + } + } + ] + }); + }); }); if (FEATURES.VIDEO) { @@ -259,6 +340,7 @@ describe('viantOrtbBidAdapter', function () { 'skip': 1, 'skipafter': 5, 'minduration': 10, + 'placement': 1, 'maxduration': 31 } }, diff --git a/test/spec/modules/vidazooBidAdapter_spec.js b/test/spec/modules/vidazooBidAdapter_spec.js index 5515002a054..14a49b21cdc 100644 --- a/test/spec/modules/vidazooBidAdapter_spec.js +++ b/test/spec/modules/vidazooBidAdapter_spec.js @@ -1,7 +1,11 @@ import {expect} from 'chai'; import { spec as adapter, + storage, createDomain, + webSessionId +} from 'modules/vidazooBidAdapter.js'; +import { hashCode, extractPID, extractCID, @@ -11,9 +15,9 @@ import { tryParseJSON, getUniqueDealId, getNextDealId, - getVidazooSessionId, - webSessionId -} from 'modules/vidazooBidAdapter.js'; + getTopWindowQueryParams, + getVidazooSessionId +} from 'libraries/vidazooUtils/bidderUtils.js' import * as utils from 'src/utils.js'; import {version} from 'package.json'; import {useFakeTimers} from 'sinon'; @@ -21,7 +25,7 @@ import {BANNER, VIDEO} from '../../../src/mediaTypes'; import {config} from '../../../src/config'; import {deepSetValue} from 'src/utils.js'; -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; +export const TEST_ID_SYSTEMS = ['criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'pubcid', 'tdid', 'pubProvidedId']; const SUB_DOMAIN = 'openrtb'; @@ -88,7 +92,7 @@ const VIDEO_BID = { 'minduration': 0, 'startdelay': 0, 'linearity': 1, - 'api': [2], + 'api': [2, 7], 'placement': 1 } } @@ -111,6 +115,7 @@ const BIDDER_REQUEST = { 'cat': ['IAB2'], 'pagecat': ['IAB2-2'], 'content': { + 'language': 'en', 'data': [{ 'name': 'example.com', 'ext': { @@ -125,7 +130,8 @@ const BIDDER_REQUEST = { }, 'regs': { 'gpp': 'gpp_string', - 'gpp_sid': [7] + 'gpp_sid': [7], + 'coppa': 0 }, 'device': { 'sua': { @@ -153,8 +159,14 @@ const BIDDER_REQUEST = { segment: [{id: '243'}], }, ], + }, + source: { + ext: { + omidpn: 'MyIntegrationPartner', + omidpv: '7.1' + } } - }, + } }; const SERVER_RESPONSE = { @@ -205,15 +217,6 @@ const REQUEST = { } }; -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - describe('VidazooBidAdapter', function () { describe('validtae spec', function () { it('exists and is a function', function () { @@ -317,6 +320,7 @@ describe('VidazooBidAdapter', function () { gpid: '', prebidVersion: version, ptrace: '1000', + vdzhum: '1000', publisherId: '59ac17c192832d0011283fe3', url: 'https%3A%2F%2Fwww.greatsite.com', referrer: 'https://www.somereferrer.com', @@ -340,6 +344,8 @@ describe('VidazooBidAdapter', function () { 'bitness': '64', 'architecture': '' }, + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -363,7 +369,7 @@ describe('VidazooBidAdapter', function () { webSessionId: webSessionId, mediaTypes: { video: { - api: [2], + api: [2, 7], context: 'instream', linearity: 1, maxduration: 60, @@ -377,7 +383,9 @@ describe('VidazooBidAdapter', function () { protocols: [2, 3, 5, 6], startdelay: 0 } - } + }, + omidpn: 'MyIntegrationPartner', + omidpv: '7.1' } }) ; @@ -436,6 +444,7 @@ describe('VidazooBidAdapter', function () { prebidVersion: version, schain: BID.schain, ptrace: '1000', + vdzhum: '1000', res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), @@ -445,6 +454,8 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -526,6 +537,7 @@ describe('VidazooBidAdapter', function () { prebidVersion: version, schain: BID.schain, ptrace: '1000', + vdzhum: '1000', res: `${window.top.screen.width}x${window.top.screen.height}`, mediaTypes: [BANNER], uqs: getTopWindowQueryParams(), @@ -535,6 +547,8 @@ describe('VidazooBidAdapter', function () { gpid: '1234567890', cat: ['IAB2'], pagecat: ['IAB2-2'], + contentLang: 'en', + coppa: 0, contentData: [{ 'name': 'example.com', 'ext': { @@ -600,7 +614,7 @@ describe('VidazooBidAdapter', function () { it('should set fledge correctly if enabled', function () { config.resetConfig(); const bidderRequest = utils.deepClone(BIDDER_REQUEST); - bidderRequest.fledgeEnabled = true; + bidderRequest.paapi = {enabled: true}; deepSetValue(bidderRequest, 'ortb2Imp.ext.ae', 1); const requests = adapter.buildRequests([BID], bidderRequest); expect(requests[0].data.fledge).to.equal(1); @@ -750,8 +764,6 @@ describe('VidazooBidAdapter', function () { switch (idSystemProvider) { case 'lipb': return {lipbid: id}; - case 'parrableId': - return {eid: id}; case 'id5id': return {uid: id}; default: @@ -802,14 +814,14 @@ describe('VidazooBidAdapter', function () { $$PREBID_GLOBAL$$.bidderSettings = {}; }); it('should get undefined vidazoo session id', function () { - const sessionId = getVidazooSessionId(); + const sessionId = getVidazooSessionId(storage); 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(); + const sessionId = getVidazooSessionId(storage); expect(sessionId).to.be.equal(vidSid); }); }); @@ -828,15 +840,15 @@ describe('VidazooBidAdapter', function () { const key = 'myDealKey'; it('should get the next deal id', function () { - const dealId = getNextDealId(key); - const nextDealId = getNextDealId(key); + const dealId = getNextDealId(storage, key); + const nextDealId = getNextDealId(storage, 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); + const dealId = getNextDealId(storage, key, 100); expect(dealId).to.be.equal(1); done(); }, 200); @@ -857,13 +869,13 @@ describe('VidazooBidAdapter', function () { const key = 'myKey'; let uniqueDealId; beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); + uniqueDealId = getUniqueDealId(storage, key, 0); }) it('should get current unique deal id', function (done) { // waiting some time so `now` will become past setTimeout(() => { - const current = getUniqueDealId(key); + const current = getUniqueDealId(storage, key); expect(current).to.be.equal(uniqueDealId); done(); }, 200); @@ -871,7 +883,7 @@ describe('VidazooBidAdapter', function () { it('should get new unique deal id on expiration', function (done) { setTimeout(() => { - const current = getUniqueDealId(key, 100); + const current = getUniqueDealId(storage, key, 100); expect(current).to.not.be.equal(uniqueDealId); done(); }, 200) @@ -895,8 +907,8 @@ describe('VidazooBidAdapter', function () { shouldAdvanceTime: true, now }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); + setStorageItem(storage, 'myKey', 2020); + const {value, created} = getStorageItem(storage, 'myKey'); expect(created).to.be.equal(now); expect(value).to.be.equal(2020); expect(typeof value).to.be.equal('number'); @@ -907,7 +919,7 @@ describe('VidazooBidAdapter', function () { it('should get external stored value', function () { const value = 'superman' window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); + const item = getStorageItem(storage, 'myExternalKey'); expect(item).to.be.equal(value); }); diff --git a/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js new file mode 100644 index 00000000000..1a792411497 --- /dev/null +++ b/test/spec/modules/videoModule/submodules/adplayerproVideoProvider_spec.js @@ -0,0 +1,521 @@ +// Using require style imports for fine grained control of import time +import { + AD_CLICK, + AD_COMPLETE, + AD_ERROR, + AD_IMPRESSION, + AD_LOADED, + AD_PAUSE, + AD_PLAY, + AD_REQUEST, + AD_SKIPPED, + AD_STARTED, + DESTROYED, + PLAYER_RESIZE, + SETUP_COMPLETE, + SETUP_FAILED, + VOLUME +} from 'libraries/video/constants/events.js'; +import adPlayerProSubmoduleFactory, {callbackStorageFactory} from '../../../../../modules/adplayerproVideoProvider.js'; +import {PLACEMENT} from '../../../../../libraries/video/constants/ortb'; +import sinon from 'sinon'; + +const {AdPlayerProProvider, utils} = require('modules/adplayerproVideoProvider.js'); + +const { + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, VPAID_MIME_TYPE +} = require('libraries/video/constants/ortb.js'); + +function getPlayerMock() { + return { + setup: function () { + return this; + }, + load: function () { + }, + resize: function () { + }, + remove: function () { + }, + on: function () { + return this; + }, + off: function () { + return this; + }, + getAdWidth: function () { + return 600 + }, + getAdHeight: function () { + return 400; + } + }; +} + +function makePlayerFactoryMock(playerMock_) { + return () => playerMock_; +} + +function getUtilsMock() { + return { + getConfig: function () { + }, + getPlayerEvent: event => event, + getSupportedMediaTypes: function () { + }, + getPlacement: function () { + }, + getPlaybackMethod: function () { + } + }; +} + +function addDiv() { + const div = document.createElement('div'); + div.setAttribute('id', 'test'); + document.body.appendChild(div); +} + +function removeDiv() { + const div = document.getElementById('test'); + if (div) { + div.remove(); + } +} + +describe('AdPlayerProProvider', function () { + let config; + let callbackStorage; + let utilsMock; + let player; + + beforeEach(() => { + addDiv(); + config = {divId: 'test', playerConfig: {placementId: 'testId'}}; + callbackStorage = callbackStorageFactory(); + utilsMock = getUtilsMock(); + player = getPlayerMock(); + }); + + afterEach(() => { + removeDiv(); + }); + + describe('init', function () { + it('should trigger failure when Adplayer.Pro is missing', function () { + const provider = AdPlayerProProvider(config, null, callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-1); + }); + + it('should trigger failure when the div is not found', function () { + config.divId = 'fake-div' + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-3); + }); + + it('should trigger failure when the placementId is not found', function () { + config.playerConfig.placementId = ''; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utilsMock); + const setupFailed = sinon.spy(); + provider.onEvent(SETUP_FAILED, setupFailed, {}); + provider.init(); + expect(setupFailed.calledOnce).to.be.true; + const payload = setupFailed.args[0][1]; + expect(payload.errorCode).to.be.equal(-4); + }); + + it('should instantiate the player after setAdTagUrl', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should instantiate the player after setAdTagUrl for adPlayerProSubmoduleFactory', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + window.playerPro = makePlayerFactoryMock(player); + const provider = adPlayerProSubmoduleFactory(config, {}); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + }); + + it('should trigger setup complete when player is already instantiated', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should support multiple setup complete event handlers', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + const setupComplete = sinon.spy(); + const setupComplete2 = sinon.spy(); + provider.onEvent(SETUP_COMPLETE, setupComplete, {}); + provider.onEvent(SETUP_COMPLETE, setupComplete2, {}); + provider.init(); + expect(setupComplete.calledOnce).to.be.true; + expect(setupComplete2.calledOnce).to.be.true; + expect(setupSpy.called).to.be.false; + }); + + it('should not reinstantiate player', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const onSpy = player.on = sinon.spy(player.on); + + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + expect(setupSpy.calledOnce).to.be.false; + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + const payload = setupSpy.args[0][0]; + expect(payload.advertising.tag.url).to.be.equal('https://test.com'); + + // test that the player is not reinitialized + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledOnce).to.be.true; + + // get and call AdStopped event + const args = onSpy.args[0]; + expect(args[0]).to.be.equal('AdStopped'); + args[1](); + + provider.setAdTagUrl('https://test.com', {}); + expect(setupSpy.calledTwice).to.be.true; + }); + }); + + describe('getId', function () { + it('should return configured div id', function () { + const provider = AdPlayerProProvider(config, undefined, undefined, utils); + expect(provider.getId()).to.be.equal('test'); + }); + }); + + describe('getOrtbVideo', function () { + it('should populate oRTB Video params', function () { + const test_media_type = VIDEO_MIME_TYPE.MP4; + const test_placement = PLACEMENT.ARTICLE; + const test_playback_method = PLAYBACK_METHODS.CLICK_TO_PLAY; + + utilsMock.getSupportedMediaTypes = () => [test_media_type]; + utilsMock.getPlacement = () => test_placement; + utilsMock.getPlaybackMethod = () => test_playback_method; + + const provider = AdPlayerProProvider(config, null, null, utilsMock); + provider.init(); + let video = provider.getOrtbVideo(); + + expect(video.mimes).to.include(VIDEO_MIME_TYPE.MP4); + expect(video.protocols).to.include.members([ + PROTOCOLS.VAST_2_0, + PROTOCOLS.VAST_3_0, + PROTOCOLS.VAST_4_0, + PROTOCOLS.VAST_2_0_WRAPPER, + PROTOCOLS.VAST_3_0_WRAPPER, + PROTOCOLS.VAST_4_0_WRAPPER + ]); + expect(video.placement).to.equal(test_placement); + expect(video.maxextended).to.equal(-1); + expect(video.boxingallowed).to.equal(1); + expect(video.playbackmethod).to.include(test_playback_method); + expect(video.playbackend).to.equal(1); + expect(video.api).to.have.length(2); + expect(video.api).to.include.members([API_FRAMEWORKS.VPAID_2_0, API_FRAMEWORKS.OMID_1_0]); // + }); + }); + + describe('getOrtbContent', function () { + it('should populate oRTB Content params', function () { + const provider = AdPlayerProProvider(config, null, null, utils); + provider.init(); + expect(provider.getOrtbContent()).to.be.undefined; + }); + }); + + describe('setAdTagUrl', function () { + it('should call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {adXml: 'https://test.com'}); + expect(setupSpy.calledOnce).to.be.true; + }); + + it('should not call setup', function () { + const setupSpy = player.setup = sinon.spy(player.setup); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('', {}); + expect(setupSpy.calledOnce).to.be.false; + }); + }); + + describe('events', function () { + it('should register event listener on player', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_REQUEST, callback, {}); + provider.onEvent('test', callback, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[0][0]).to.be.equal('AdStopped'); + expect(onSpy.args[1][0]).to.be.equal('AdRequest'); + }); + + it('should remove event listener on player', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION, callback); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + const eventCallback = offSpy.args[0][1]; + expect(eventName).to.be.equal('AdImpression'); + expect(eventCallback).to.be.exist; + }); + + it('should remove event listener on player by eventName', function () { + const offSpy = player.off = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callback = () => { + }; + provider.onEvent(AD_IMPRESSION, callback, {}); + provider.offEvent(AD_IMPRESSION); + expect(offSpy.calledOnce).to.be.true; + const eventName = offSpy.args[0][0]; + expect(eventName).to.be.equal('AdImpression'); + }); + + it('should call event player resize', function () { + const onSpy = player.on = sinon.spy(); + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + const callbackSpy = sinon.spy(); + provider.onEvent(PLAYER_RESIZE, callbackSpy, {}); + expect(onSpy.calledTwice).to.be.true; + expect(onSpy.args[1][0]).to.be.equal('AdSizeChange'); + expect(callbackSpy.notCalled).to.be.true; + onSpy.args[1][1](); + expect(callbackSpy.calledOnce).to.be.true; + expect(callbackSpy.args[0][1].width).to.be.equal(600); + expect(callbackSpy.args[0][1].height).to.be.equal(400); + }); + }); + + describe('destroy', function () { + it('should remove and null the player', function () { + const removeSpy = player.remove = sinon.spy(); + player.remove = removeSpy; + const provider = AdPlayerProProvider(config, makePlayerFactoryMock(player), callbackStorage, utils); + provider.init(); + provider.setAdTagUrl('https://test.com', {}); + provider.destroy(); + provider.destroy(); + expect(removeSpy.calledOnce).to.be.true; + }); + }); +}); + +describe('AdPlayerProProvider utils', function () { + it('getConfig', function () { + expect(utils.getConfig()).to.be.undefined; + expect(utils.getConfig({})).to.be.undefined; + const config = utils.getConfig({}, 'https://test.com'); + expect(config.advertising.tag.url).to.be.equal('https://test.com'); + expect(config._pType).to.be.equal('pbjs'); + }); + + it('getPlayerEvent', function () { + function test(event, expected) { + expect(utils.getPlayerEvent(event)).to.be.equal(expected); + } + + test(DESTROYED, 'AdStopped'); + test(AD_REQUEST, 'AdRequest'); + test(AD_LOADED, 'AdLoaded'); + test(AD_STARTED, 'AdStarted'); + test(AD_IMPRESSION, 'AdImpression'); + test(AD_PLAY, 'AdPlaying'); + test(AD_PAUSE, 'AdPaused'); + test(AD_CLICK, 'AdClickThru'); + test(AD_SKIPPED, 'AdSkipped'); + test(AD_ERROR, 'AdError'); + test(AD_COMPLETE, 'AdCompleted'); + test(VOLUME, 'AdVolumeChange'); + test(PLAYER_RESIZE, 'AdSizeChange'); + test('test', 'test'); + }); + + it('getSupportedMediaTypes', function () { + let supportedMediaTypes = utils.getSupportedMediaTypes([]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + + supportedMediaTypes = utils.getSupportedMediaTypes([VIDEO_MIME_TYPE.MP4]); + expect(supportedMediaTypes).to.include(VPAID_MIME_TYPE); + }); + + it('getPlacement', function () { + function test(config, expected) { + expect(utils.getPlacement(config)).to.be.equal(expected); + } + + test(false, PLACEMENT.BANNER); + test({}, PLACEMENT.BANNER); + test({type: 'test'}, PLACEMENT.BANNER); + test({type: 'inPage'}, PLACEMENT.ARTICLE); + test({type: 'rewarded'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + test({type: 'inView'}, PLACEMENT.INTERSTITIAL_SLIDER_FLOATING); + }); + + it('getPlaybackMethod', function () { + function test(autoplay, mute, expected) { + expect(utils.getPlaybackMethod({autoplay, mute})).to.be.equal(expected); + } + + test(false, false, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(false, true, PLAYBACK_METHODS.CLICK_TO_PLAY); + test(true, false, PLAYBACK_METHODS.AUTOPLAY); + test(true, true, PLAYBACK_METHODS.AUTOPLAY_MUTED); + }); +}); + +describe('AdPlayerProProvider callbackStorageFactory', function () { + let player; + let callbackStorage; + + beforeEach(() => { + player = getPlayerMock(); + callbackStorage = callbackStorageFactory(); + }); + + it('storeCallback and getCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + const callback2 = () => 21; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + expect(callbackStorage.getCallback(eventType, callback2)).to.be.equal(eventHandler2); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + }); + + it('clearCallback', function () { + const eventType = 'test'; + const callback = () => 11; + const callback2 = () => 21; + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + expect(callbackStorage.getCallback(eventType, callback)()).to.be.equal(12); + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + + callbackStorage.clearCallback(eventType); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)).to.be.undefined; + + callbackStorage.storeCallback(eventType, () => 12, callback); + callbackStorage.storeCallback(eventType, () => 22, callback2); + + callbackStorage.clearCallback(eventType, callback); + + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + expect(callbackStorage.getCallback(eventType, callback2)()).to.be.equal(22); + }); + + it('addAllCallbacks', function () { + const eventType = 'test'; + const eventType2 = 'test2'; + const callback = () => 11; + const callback2 = () => 21; + const eventHandler = () => 12; + const eventHandler2 = () => 22; + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType, eventHandler2, callback2); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler2, callback2); + + let spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(4); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler2)).to.be.true; + + callbackStorage.clearCallback(eventType2); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(1); + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + + callbackStorage.storeCallback(eventType, eventHandler, callback); + callbackStorage.storeCallback(eventType2, eventHandler, callback); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(3); + expect(spy.calledWith(eventType, eventHandler)).to.be.true; + expect(spy.calledWith(eventType, eventHandler2)).to.be.true; + expect(spy.calledWith(eventType2, eventHandler)).to.be.true; + + callbackStorage.clearStorage(); + spy = sinon.spy(); + callbackStorage.addAllCallbacks(spy); + expect(spy.callCount).to.be.equal(0); + }); + + it('clearStorage', function () { + const eventType = 'test'; + const callback = () => 11; + const eventHandler = () => 12; + callbackStorage.storeCallback(eventType, eventHandler, callback); + expect(callbackStorage.getCallback(eventType, callback)).to.be.equal(eventHandler); + + callbackStorage.clearStorage(); + expect(callbackStorage.getCallback(eventType, callback)).to.be.undefined; + }); +}); diff --git a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js index a7379ccbab2..125f608f803 100644 --- a/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js +++ b/test/spec/modules/videoModule/submodules/videojsVideoProvider_spec.js @@ -6,7 +6,7 @@ import { const {VideojsProvider, utils} = require('modules/videojsVideoProvider'); const { - PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLACEMENT, VPAID_MIME_TYPE, AD_POSITION + PROTOCOLS, API_FRAMEWORKS, VIDEO_MIME_TYPE, PLAYBACK_METHODS, PLCMT, VPAID_MIME_TYPE, AD_POSITION } = require('libraries/video/constants/ortb.js'); const videojs = require('video.js').default; @@ -139,7 +139,7 @@ describe('videojsProvider', function () { expect(video.playbackmethod).to.include(PLAYBACK_METHODS.CLICK_TO_PLAY); expect(video.playbackend).to.equal(1); expect(video.api).to.deep.equal([2]); - expect(video.placement).to.be.equal(PLACEMENT.INSTREAM); + expect(video.plcmt).to.be.equal(PLCMT.ACCOMPANYING_CONTENT); }); it('should populate oRTB Content', function () { diff --git a/test/spec/modules/videoreachBidAdapter_spec.js b/test/spec/modules/videoreachBidAdapter_spec.js index 67ad89eac3d..dc81ec74ff8 100644 --- a/test/spec/modules/videoreachBidAdapter_spec.js +++ b/test/spec/modules/videoreachBidAdapter_spec.js @@ -21,12 +21,12 @@ describe('videoreachBidAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'TagId': '' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/vidoomyBidAdapter_spec.js b/test/spec/modules/vidoomyBidAdapter_spec.js index 38fa872e6b8..6fd779bdb0b 100644 --- a/test/spec/modules/vidoomyBidAdapter_spec.js +++ b/test/spec/modules/vidoomyBidAdapter_spec.js @@ -44,9 +44,9 @@ describe('vidoomyBidAdapter', function() { }); 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); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when mediaType is video with INSTREAM context and lacks playerSize property', function () { diff --git a/test/spec/modules/viqeoBidAdapter_spec.js b/test/spec/modules/viqeoBidAdapter_spec.js index 8f597318af9..af397393a51 100644 --- a/test/spec/modules/viqeoBidAdapter_spec.js +++ b/test/spec/modules/viqeoBidAdapter_spec.js @@ -6,9 +6,7 @@ describe('viqeoBidAdapter', function () { expect(spec.isBidRequestValid({ bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', playerOptions: { videoId: 'ed584da454c7205ca7e4', profileId: 1382, @@ -27,9 +25,7 @@ describe('viqeoBidAdapter', function () { bidId: 'id1', bidder: 'viqeo', params: { - user: { - buyeruid: '1', - }, + tagId: '2', currency: 'EUR', floor: 0.5, playerOptions: { @@ -48,7 +44,7 @@ describe('viqeoBidAdapter', function () { expect(requestData.imp[0].bidfloor).to.equal(0.5); expect(requestData.imp[0].video.w).to.equal(240); expect(requestData.imp[0].video.h).to.equal(400); - expect(requestData.user.buyeruid).to.equal('1'); + expect(requestData.imp[0].tagid).to.equal('2'); }); it('build request check url', function () { const bidRequestData = [{ @@ -58,14 +54,13 @@ describe('viqeoBidAdapter', function () { videoId: 'ed584da454c7205ca7e4', profileId: 1382, }, - sspId: 42, }, mediaTypes: { video: { playerSize: [[240, 400]] } }, }]; const request = spec.buildRequests(bidRequestData); - expect(request[0].url).to.equal('https://ads.betweendigital.com/openrtb_bid/?sspId=42') + expect(request[0].url).to.equal('https://ad.vqserve.com/ads/prebid') }); it('response_params common case', function () { const bidRequestData = { diff --git a/test/spec/modules/visiblemeasuresBidAdapter_spec.js b/test/spec/modules/visiblemeasuresBidAdapter_spec.js index ad75e17699f..55f2a74ce77 100644 --- a/test/spec/modules/visiblemeasuresBidAdapter_spec.js +++ b/test/spec/modules/visiblemeasuresBidAdapter_spec.js @@ -3,11 +3,21 @@ import { spec } from '../../../modules/visiblemeasuresBidAdapter.js'; import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; -const bidder = 'visiblemeasures' +const bidder = 'visiblemeasures'; const adUrl = 'https://us-e.visiblemeasures.com/pbjs'; const syncUrl = 'https://cs.visiblemeasures.com'; describe('VisibleMeasuresBidAdapter', function () { + const userIdAsEids = [{ + source: 'test.org', + uids: [{ + id: '01**********', + atype: 1, + ext: { + third: '01***********' + } + }] + }]; const bids = [ { bidId: getUniqueIdentifierStr(), @@ -18,8 +28,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testBanner', - } + placementId: 'testBanner' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -32,8 +43,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testVideo', - } + placementId: 'testVideo' + }, + userIdAsEids }, { bidId: getUniqueIdentifierStr(), @@ -55,8 +67,9 @@ describe('VisibleMeasuresBidAdapter', function () { } }, params: { - placementId: 'testNative', - } + placementId: 'testNative' + }, + userIdAsEids } ]; @@ -75,9 +88,20 @@ describe('VisibleMeasuresBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + vendorData: {} + }, refererInfo: { - referer: 'https://test.com' + referer: 'https://test.com', + page: 'https://test.com' + }, + ortb2: { + device: { + w: 1512, + h: 982, + language: 'en-UK' + } }, timeout: 500 }; @@ -114,6 +138,7 @@ describe('VisibleMeasuresBidAdapter', function () { expect(data).to.be.an('object'); expect(data).to.have.all.keys('deviceWidth', 'deviceHeight', + 'device', 'language', 'secure', 'host', @@ -131,7 +156,7 @@ describe('VisibleMeasuresBidAdapter', function () { 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.gdpr).to.be.a('object'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -147,6 +172,56 @@ describe('VisibleMeasuresBidAdapter', function () { expect(placement.schain).to.be.an('object'); expect(placement.bidfloor).to.exist.and.to.equal(0); expect(placement.type).to.exist.and.to.equal('publisher'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); + + 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 valid endpoints', function () { + const bids = [ + { + bidId: getUniqueIdentifierStr(), + bidder: bidder, + mediaTypes: { + [BANNER]: { + sizes: [[300, 250]] + } + }, + params: { + endpointId: 'testBanner', + }, + userIdAsEids + } + ]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + + const { placements } = serverRequest.data; + for (let i = 0, len = placements.length; i < len; i++) { + const placement = placements[i]; + expect(placement.endpointId).to.be.oneOf(['testBanner', 'testVideo', 'testNative']); + expect(placement.adFormat).to.be.oneOf([BANNER, VIDEO, NATIVE]); + expect(placement.bidId).to.be.a('string'); + expect(placement.schain).to.be.an('object'); + expect(placement.bidfloor).to.exist.and.to.equal(0); + expect(placement.type).to.exist.and.to.equal('network'); + expect(placement.eids).to.exist.and.to.be.deep.equal(userIdAsEids); if (placement.adFormat === BANNER) { expect(placement.sizes).to.be.an('array'); @@ -172,8 +247,10 @@ describe('VisibleMeasuresBidAdapter', function () { 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.gdpr).to.be.a('object'); + expect(data.gdpr).to.have.property('consentString'); + expect(data.gdpr).to.not.have.property('vendorData'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); @@ -188,12 +265,38 @@ describe('VisibleMeasuresBidAdapter', function () { expect(data.ccpa).to.equal(bidderRequest.uspConsent); expect(data.gdpr).to.not.exist; }); + }); + + describe('gpp consent', function () { + it('bidderRequest.gppConsent', () => { + bidderRequest.gppConsent = { + gppString: 'abc123', + applicableSections: [8] + }; - it('Returns empty data if no valid requests are passed', function () { - serverRequest = spec.buildRequests([], bidderRequest); + let serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest.data; - expect(data.placements).to.be.an('array').that.is.empty; - }); + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + delete bidderRequest.gppConsent; + }) + + it('bidderRequest.ortb2.regs.gpp', () => { + bidderRequest.ortb2 = bidderRequest.ortb2 || {}; + bidderRequest.ortb2.regs = bidderRequest.ortb2.regs || {}; + bidderRequest.ortb2.regs.gpp = 'abc123'; + bidderRequest.ortb2.regs.gpp_sid = [8]; + + let serverRequest = spec.buildRequests(bids, bidderRequest); + let data = serverRequest.data; + expect(data).to.be.an('object'); + expect(data).to.have.property('gpp'); + expect(data).to.have.property('gpp_sid'); + + bidderRequest.ortb2; + }) }); describe('interpretResponse', function () { @@ -397,5 +500,17 @@ describe('VisibleMeasuresBidAdapter', function () { expect(syncData[0].url).to.be.a('string') expect(syncData[0].url).to.equal(`${syncUrl}/image?pbjs=1&ccpa_consent=1---&coppa=0`) }); + it('Should return array of objects with proper sync config , include GPP', function() { + const syncData = spec.getUserSyncs({}, {}, {}, {}, { + gppString: 'abc123', + applicableSections: [8] + }); + 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(`${syncUrl}/image?pbjs=1&gpp=abc123&gpp_sid=8&coppa=0`); + }); }); }); diff --git a/test/spec/modules/visxBidAdapter_spec.js b/test/spec/modules/visxBidAdapter_spec.js index 5528705efd7..db928e4d802 100755 --- a/test/spec/modules/visxBidAdapter_spec.js +++ b/test/spec/modules/visxBidAdapter_spec.js @@ -4,6 +4,7 @@ import { config } from 'src/config.js'; import { newBidder } from 'src/adapters/bidderFactory.js'; import * as utils from 'src/utils.js'; import { makeSlot } from '../integration/faker/googletag.js'; +import { mergeDeep } from '../../../src/utils.js'; describe('VisxAdapter', function () { const adapter = newBidder(spec); @@ -32,21 +33,21 @@ describe('VisxAdapter', function () { }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when uid can not be parsed as number', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'uid': 'sdvsdv' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('it should fail on invalid video bid', function () { @@ -89,6 +90,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -109,7 +150,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -120,7 +161,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90], [300, 250]], 'bidId': '3150ccb55da321', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -131,7 +172,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '42dbe3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -151,7 +192,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39a4e3a7168a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; @@ -217,7 +258,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -235,7 +313,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -255,7 +370,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['GBP'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); getConfigStub.restore(); @@ -277,7 +429,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['USD'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, }); getConfigStub.restore(); @@ -294,9 +483,50 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'regs': { + 'ext': { + 'gdpr': 1 + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, - 'regs': {'ext': {'gdpr': 1}} }); }); @@ -311,7 +541,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 0}} }); @@ -328,7 +595,44 @@ describe('VisxAdapter', function () { 'tmax': 3000, 'cur': ['EUR'], 'source': {'ext': {'wrapperType': 'Prebid_js', 'wrapperVersion': '$prebid.version$'}}, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'consent': 'AAA'}}, 'regs': {'ext': {'gdpr': 1}} }); @@ -359,7 +663,44 @@ describe('VisxAdapter', function () { 'schain': schainObject } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -408,7 +749,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer}, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, 'user': {'ext': {'eids': eids}} }); }); @@ -429,7 +807,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -448,6 +863,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -467,7 +922,7 @@ describe('VisxAdapter', function () { }, 'bidId': '39aff3a7169a6a', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a476', + 'auctionId': '1d1a030790a476' } ]; @@ -494,7 +949,6 @@ describe('VisxAdapter', function () { const payload = parseRequest(request.url); expect(payload).to.be.an('object'); expect(payload).to.have.property('auids', '903538'); - const postData = request.data; expect(postData).to.be.an('object'); expect(postData).to.deep.equal({ @@ -512,7 +966,50 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ + '124' + ] + }, + { + 'brand': 'Google Chrome', + 'version': [ + '124' + ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ + '99' + ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + } }); }); }); @@ -531,6 +1028,46 @@ describe('VisxAdapter', function () { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + ortb2: { + device: { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + site: { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; const referrer = bidderRequest.refererInfo.page; @@ -544,7 +1081,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' }, { 'bidder': 'visx', @@ -555,7 +1092,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; let sandbox; @@ -612,7 +1149,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); @@ -641,7 +1215,44 @@ describe('VisxAdapter', function () { 'wrapperVersion': '$prebid.version$' } }, - 'site': {'page': referrer} + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } }); }); }); @@ -669,7 +1280,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -719,7 +1330,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -730,7 +1341,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4dff80cc4ee346', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' }, { 'bidder': 'visx', @@ -741,7 +1352,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '5703af74d0472a', 'bidderRequestId': '2c2bb1972df9a', - 'auctionId': '1fa09aee5c8c99', + 'auctionId': '1fa09aee5c8c99' } ]; const request = spec.buildRequests(bidRequests); @@ -823,7 +1434,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const getConfigStub = sinon.stub(config, 'getConfig').returns('PLN'); @@ -888,7 +1499,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71321', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' }, { 'bidder': 'visx', @@ -899,7 +1510,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '300bfeb0d7183bb', 'bidderRequestId': '2c2bb1972d23af', - 'auctionId': '1fa09aee5c84d34', + 'auctionId': '1fa09aee5c84d34' } ]; const request = spec.buildRequests(bidRequests); @@ -925,7 +1536,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -936,7 +1547,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '326bde7fbf69', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -947,7 +1558,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '4e111f1b66e4', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -958,7 +1569,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '26d6f897b516', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' }, { 'bidder': 'visx', @@ -969,7 +1580,7 @@ describe('VisxAdapter', function () { 'sizes': [[728, 90]], 'bidId': '1751cd90161', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1075,7 +1686,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '5126e301f4be', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1086,7 +1697,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '57b2ebe70e16', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' }, { 'bidder': 'visx', @@ -1097,7 +1708,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '225fcd44b18c', 'bidderRequestId': '171c5405a390', - 'auctionId': '35bcbc0f7e79c', + 'auctionId': '35bcbc0f7e79c' } ]; const request = spec.buildRequests(bidRequests); @@ -1162,7 +1773,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1214,7 +1825,7 @@ describe('VisxAdapter', function () { 'sizes': [[400, 300]], 'bidId': '2164be6358b9', 'bidderRequestId': '106efe3247', - 'auctionId': '32a1f276cb87cb8', + 'auctionId': '32a1f276cb87cb8' } ]; const request = spec.buildRequests(bidRequests); @@ -1251,7 +1862,7 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '300bfeb0d71a5b', 'bidderRequestId': '5f2009617a7c0a', - 'auctionId': '1cbd2feafe5e8b', + 'auctionId': '1cbd2feafe5e8b' } ]; const request = spec.buildRequests(bidRequests); @@ -1434,13 +2045,53 @@ describe('VisxAdapter', function () { 'sizes': [[300, 250], [300, 600]], 'bidId': '30b31c1838de1e', 'bidderRequestId': '22edbae2733bf6', - 'auctionId': '1d1a030790a475', + 'auctionId': '1d1a030790a475' } ]; const bidderRequest = { timeout: 3000, refererInfo: { page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + } } }; @@ -1511,4 +2162,183 @@ describe('VisxAdapter', function () { expect(request.data.user.ext.vads).to.be.a('string'); }); }); + + describe('ortb2 data', function () { + const bidRequests = [ + { + 'bidder': 'visx', + 'params': { + 'uid': 903535 + }, + 'adUnitCode': 'adunit-code-1', + 'sizes': [[300, 250], [300, 600]], + 'bidId': '30b31c1838de1e', + 'bidderRequestId': '22edbae2733bf6', + 'auctionId': '1d1a030790a475' + } + ]; + const bidderRequest = { + timeout: 3000, + refererInfo: { + page: 'https://example.com' + }, + 'ortb2': { + 'device': { + 'w': 1259, + 'h': 934, + 'dnt': 0, + 'ua': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36', + 'language': 'tr', + 'sua': { + 'source': 1, + 'platform': { + 'brand': 'macOS' + }, + 'browsers': [ + { + 'brand': 'Chromium', + 'version': [ '124' ] + }, + { + 'brand': 'Google Chrome', + 'version': [ '124' ] + }, + { + 'brand': 'Not-A.Brand', + 'version': [ '99' ] + } + ], + 'mobile': 0 + }, + 'ext': { + 'cdep': 'treatment_1.1' + } + }, + 'site': { + 'domain': 'localhost:9999', + 'publisher': { + 'domain': 'localhost:9999' + }, + 'page': 'http://localhost:9999/integrationExamples/gpt/hello_world.html' + }, + 'user': { + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + }, + { + 'ext': { + 'segtax': 601, + 'segclass': '5' + }, + 'segment': [ + { + 'id': '140' + } + ], + 'name': 'pa.openx.net' + }, + { + 'ext': { + 'segtax': 601, + 'segclass': '5' + }, + 'segment': [ + { + 'id': '140' + } + ], + 'name': 'ads.pubmatic.com' + } + ], + 'ext': { + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + } + } + } + }; + + it('should pass interests if ortb2 has interests in user data', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.user.ext.data.interests).not.to.be.undefined; + }); + + it('should pass device if ortb2 has device', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.device).not.to.be.undefined; + }); + + it('should pass site if ortb2 has site', function () { + const request = spec.buildRequests(bidRequests, bidderRequest); + expect(request.data.site).not.to.be.undefined; + }); + + it('should merge if user object exists', function () { + const user = { + 'ext': { + 'vads': 'cXaIRA425BmynEN1ratEnc_5e', + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + }, + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + } + ] + }; + const userOrtb2 = { + 'keywords': 'x,y', + 'data': [ + { + 'name': 'exampleprovider.de', + 'ext': { + 'segtax': 5 + }, + 'segment': [ + { + 'id': '1' + } + ] + } + ], + 'ext': { + 'data': { + 'registered': true, + 'interests': [ + 'ads' + ] + } + } + } + const userReq = mergeDeep(user, userOrtb2); + expect(userReq.ext.vads).not.to.be.undefined; + }); + }); }); diff --git a/test/spec/modules/weboramaRtdProvider_spec.js b/test/spec/modules/weboramaRtdProvider_spec.js index d562d9ffd13..964c42eff07 100644 --- a/test/spec/modules/weboramaRtdProvider_spec.js +++ b/test/spec/modules/weboramaRtdProvider_spec.js @@ -1509,7 +1509,7 @@ describe('weboramaRtdProvider', function() { } }; const data = { - webo_cs: ['foo', 'bar'], + webo_cs: [12, 345], webo_audiences: ['baz'], }; @@ -1620,7 +1620,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -1746,7 +1746,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -1868,7 +1868,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2013,7 +2013,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2156,7 +2156,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2217,7 +2217,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2289,7 +2289,7 @@ describe('weboramaRtdProvider', function() { expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal({ foo: ['bar'], webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }); expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.deep.equal({ inventory: { @@ -2312,7 +2312,7 @@ describe('weboramaRtdProvider', function() { it('should use default profile in case of nothing on local storage', function() { const defaultProfile = { - webo_audiences: ['baz'] + webo_audiences: [12345] }; const moduleConfig = { params: { @@ -2377,9 +2377,85 @@ describe('weboramaRtdProvider', function() { }) }); + it('should use default profile in case of something malformed on local storage', function() { + const defaultProfile = { + webo_audiences: [12345] + }; + const moduleConfig = { + params: { + weboUserDataConf: { + accoundId: 12345, + setPrebidTargeting: true, + defaultProfile: defaultProfile, + } + } + }; + + const malformed = { + targeting: { + webo_audiences: 'must be an array, not a string', + }, + }; + + sandbox.stub(storage, 'hasLocalStorage').returns(true); + sandbox.stub(storage, 'localStorageIsEnabled').returns(true); + sandbox.stub(storage, 'getDataFromLocalStorage') + .withArgs(DEFAULT_LOCAL_STORAGE_USER_PROFILE_KEY) + .returns(JSON.stringify(malformed)); + + const adUnitCode = 'adunit1'; + const reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {} + }, + adUnits: [{ + code: adUnitCode, + bids: [{ + bidder: 'smartadserver' + }, { + bidder: 'pubmatic' + }, { + bidder: 'appnexus' + }, { + bidder: 'rubicon' + }, { + bidder: 'other' + }] + }] + }; + const onDoneSpy = sinon.spy(); + + expect(weboramaSubmodule.init(moduleConfig)).to.be.true; + weboramaSubmodule.getBidRequestData(reqBidsConfigObj, onDoneSpy, moduleConfig); + + expect(onDoneSpy.calledOnce).to.be.true; + + const targeting = weboramaSubmodule.getTargetingData([adUnitCode], moduleConfig); + + expect(targeting).to.deep.equal({ + 'adunit1': defaultProfile, + }); + + expect(reqBidsConfigObj.adUnits[0].bids.length).to.equal(5); + expect(reqBidsConfigObj.adUnits[0].bids[0].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[1].params).to.be.undefined; + expect(reqBidsConfigObj.adUnits[0].bids[2].params.keywords).to.deep.equal(defaultProfile); + expect(reqBidsConfigObj.adUnits[0].bids[3].params).to.be.undefined; + ['smartadserver', 'pubmatic', 'appnexus', 'rubicon', 'other'].forEach((v) => { + expect(reqBidsConfigObj.ortb2Fragments.bidder[v]).to.deep.equal({ + user: { + ext: { + data: defaultProfile + }, + } + }); + }) + }); + it('should use default profile if cant read from local storage', function() { const defaultProfile = { - webo_audiences: ['baz'] + webo_audiences: [12345] }; let onDataResponse = {}; const moduleConfig = { @@ -2489,7 +2565,7 @@ describe('weboramaRtdProvider', function() { }; const data = { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], }; const entry = { @@ -2550,7 +2626,7 @@ describe('weboramaRtdProvider', function() { expect(targeting).to.deep.equal({ 'adunit1': { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], webo_foo: ['bar'], }, 'adunit2': data, @@ -2569,7 +2645,7 @@ describe('weboramaRtdProvider', function() { ext: { data: { webo_cs: ['foo', 'bar'], - webo_audiences: ['baz'], + webo_audiences: [12345], webo_bar: ['baz'], } }, diff --git a/test/spec/modules/winrBidAdapter_spec.js b/test/spec/modules/winrBidAdapter_spec.js index 95d1473d1cb..b0d8d72f0a1 100644 --- a/test/spec/modules/winrBidAdapter_spec.js +++ b/test/spec/modules/winrBidAdapter_spec.js @@ -93,22 +93,22 @@ describe('WinrAdapter', function () { }); it('should return false when mediaType is not banner', function () { - let bid = Object.assign({}, bid); - delete bid.mediaTypes; - bid.mediaTypes = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.mediaTypes; + invalidBid.mediaTypes = { 'video': {} }; - expect(getMediaTypeFromBid(bid)).to.not.equal('banner'); - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(getMediaTypeFromBid(invalidBid)).to.not.equal('banner'); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when required params are not passed', function () { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 0 }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/wipesBidAdapter_spec.js b/test/spec/modules/wipesBidAdapter_spec.js index a458dcf69c8..a45e324f4fd 100644 --- a/test/spec/modules/wipesBidAdapter_spec.js +++ b/test/spec/modules/wipesBidAdapter_spec.js @@ -29,9 +29,9 @@ describe('wipesBidAdapter', function () { }); 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); + let invalidBid = Object.assign({}, bid); + invalidBid.params = {}; + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); diff --git a/test/spec/modules/wurflRtdProvider_spec.js b/test/spec/modules/wurflRtdProvider_spec.js new file mode 100644 index 00000000000..0ac324ef8b2 --- /dev/null +++ b/test/spec/modules/wurflRtdProvider_spec.js @@ -0,0 +1,453 @@ +import { + bidderData, + enrichBidderRequest, + lowEntropyData, + wurflSubmodule, + makeOrtb2DeviceType, +} from 'modules/wurflRtdProvider'; +import * as ajaxModule from 'src/ajax'; +import { loadExternalScriptStub } from 'test/mocks/adloaderStub.js'; + +describe('wurflRtdProvider', function () { + describe('wurflSubmodule', function () { + const altHost = 'http://example.local/wurfl.js'; + + const wurfl_pbjs = { + low_entropy_caps: ['is_mobile', 'complete_device_name', 'form_factor'], + caps: ['advertised_browser', 'advertised_browser_version', 'advertised_device_os', 'advertised_device_os_version', 'ajax_support_javascript', 'brand_name', 'complete_device_name', 'density_class', 'form_factor', 'is_android', 'is_app_webview', 'is_connected_tv', 'is_full_desktop', 'is_ios', 'is_mobile', 'is_ott', 'is_phone', 'is_robot', 'is_smartphone', 'is_smarttv', 'is_tablet', 'manufacturer_name', 'marketing_name', 'max_image_height', 'max_image_width', 'model_name', 'physical_screen_height', 'physical_screen_width', 'pixel_density', 'pointing_method', 'resolution_height', 'resolution_width'], + authorized_bidders: { + bidder1: [0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, 12, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], + bidder2: [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 20, 21, 25, 28, 30, 31] + } + } + const WURFL = { + advertised_browser: 'Chrome Mobile', + advertised_browser_version: '130.0.0.0', + advertised_device_os: 'Android', + advertised_device_os_version: '6.0', + ajax_support_javascript: !0, + brand_name: 'Google', + complete_device_name: 'Google Nexus 5', + density_class: '3.0', + form_factor: 'Feature Phone', + is_android: !0, + is_app_webview: !1, + is_connected_tv: !1, + is_full_desktop: !1, + is_ios: !1, + is_mobile: !0, + is_ott: !1, + is_phone: !0, + is_robot: !1, + is_smartphone: !1, + is_smarttv: !1, + is_tablet: !1, + manufacturer_name: 'LG', + marketing_name: '', + max_image_height: 640, + max_image_width: 360, + model_name: 'Nexus 5', + physical_screen_height: 110, + physical_screen_width: 62, + pixel_density: 443, + pointing_method: 'touchscreen', + resolution_height: 1920, + resolution_width: 1080 + }; + + // expected analytics values + const expectedStatsURL = 'https://prebid.wurflcloud.com/v1/prebid/stats'; + const expectedData = JSON.stringify({ bidders: ['bidder1', 'bidder2'] }); + + let sandbox; + + beforeEach(function () { + sandbox = sinon.createSandbox(); + window.WURFLPromises = { + init: new Promise(function (resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), + complete: new Promise(function (resolve, reject) { resolve({ WURFL, wurfl_pbjs }) }), + }; + }); + + afterEach(() => { + // Restore the original functions + sandbox.restore(); + window.WURFLPromises = undefined; + }); + + // Bid request config + const reqBidsConfigObj = { + adUnits: [{ + bids: [ + { bidder: 'bidder1' }, + { bidder: 'bidder2' }, + { bidder: 'bidder3' }, + ] + }], + ortb2Fragments: { + global: { + device: {}, + }, + bidder: {}, + } + }; + + it('initialises the WURFL RTD provider', function () { + expect(wurflSubmodule.init()).to.be.true; + }); + + it('should enrich the bid request data', (done) => { + const expectedURL = new URL(altHost); + expectedURL.searchParams.set('debug', true); + expectedURL.searchParams.set('mode', 'prebid'); + + const callback = () => { + const v = { + bidder1: { + device: { + make: 'Google', + model: 'Nexus 5', + devicetype: 1, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: '3.0', + js: true, + ext: { + wurfl: { + advertised_browser: 'Chrome Mobile', + advertised_browser_version: '130.0.0.0', + advertised_device_os: 'Android', + advertised_device_os_version: '6.0', + ajax_support_javascript: !0, + brand_name: 'Google', + complete_device_name: 'Google Nexus 5', + density_class: '3.0', + form_factor: 'Feature Phone', + is_app_webview: !1, + is_connected_tv: !1, + is_full_desktop: !1, + is_mobile: !0, + is_ott: !1, + is_phone: !0, + is_robot: !1, + is_smartphone: !1, + is_smarttv: !1, + is_tablet: !1, + manufacturer_name: 'LG', + marketing_name: '', + max_image_height: 640, + max_image_width: 360, + model_name: 'Nexus 5', + physical_screen_height: 110, + physical_screen_width: 62, + pixel_density: 443, + pointing_method: 'touchscreen', + resolution_height: 1920, + resolution_width: 1080 + }, + }, + }, + }, + bidder2: { + device: { + make: 'Google', + model: 'Nexus 5', + devicetype: 1, + os: 'Android', + osv: '6.0', + hwv: 'Nexus 5', + h: 1920, + w: 1080, + ppi: 443, + pxratio: '3.0', + js: true, + ext: { + wurfl: { + advertised_device_os: 'Android', + advertised_device_os_version: '6.0', + ajax_support_javascript: !0, + brand_name: 'Google', + complete_device_name: 'Google Nexus 5', + density_class: '3.0', + form_factor: 'Feature Phone', + is_android: !0, + is_app_webview: !1, + is_connected_tv: !1, + is_full_desktop: !1, + is_ios: !1, + is_mobile: !0, + is_ott: !1, + is_phone: !0, + is_tablet: !1, + manufacturer_name: 'LG', + model_name: 'Nexus 5', + pixel_density: 443, + resolution_height: 1920, + resolution_width: 1080 + }, + }, + }, + }, + bidder3: { + device: { + make: 'Google', + model: 'Nexus 5', + ext: { + wurfl: { + complete_device_name: 'Google Nexus 5', + form_factor: 'Feature Phone', + is_mobile: !0, + model_name: 'Nexus 5', + brand_name: 'Google', + }, + }, + }, + }, + }; + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal(v); + done(); + }; + + const config = { + params: { + altHost: altHost, + debug: true, + } + }; + const userConsent = {}; + + wurflSubmodule.getBidRequestData(reqBidsConfigObj, callback, config, userConsent); + expect(loadExternalScriptStub.calledOnce).to.be.true; + const loadExternalScriptCall = loadExternalScriptStub.getCall(0); + expect(loadExternalScriptCall.args[0]).to.equal(expectedURL.toString()); + expect(loadExternalScriptCall.args[2]).to.equal('wurfl'); + }); + + it('onAuctionEndEvent: should send analytics data using navigator.sendBeacon, if available', () => { + const auctionDetails = {}; + const config = {}; + const userConsent = {}; + + const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon'); + + // Call the function + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.calledOnce).to.be.true; + expect(sendBeaconStub.calledWithExactly(expectedStatsURL, expectedData)).to.be.true; + }); + + it('onAuctionEndEvent: should send analytics data using fetch as fallback, if navigator.sendBeacon is not available', () => { + const auctionDetails = {}; + const config = {}; + const userConsent = {}; + + const sendBeaconStub = sandbox.stub(navigator, 'sendBeacon').value(undefined); + const windowFetchStub = sandbox.stub(window, 'fetch'); + const fetchAjaxStub = sandbox.stub(ajaxModule, 'fetch'); + + // Call the function + wurflSubmodule.onAuctionEndEvent(auctionDetails, config, userConsent); + + // Assertions + expect(sendBeaconStub.called).to.be.false; + + expect(fetchAjaxStub.calledOnce).to.be.true; + const fetchAjaxCall = fetchAjaxStub.getCall(0); + expect(fetchAjaxCall.args[0]).to.equal(expectedStatsURL); + expect(fetchAjaxCall.args[1].method).to.equal('POST'); + expect(fetchAjaxCall.args[1].body).to.equal(expectedData); + expect(fetchAjaxCall.args[1].mode).to.equal('no-cors'); + }); + }); + + describe('bidderData', () => { + it('should return the WURFL data for a bidder', () => { + const wjsData = { + capability1: 'value1', + capability2: 'value2', + capability3: 'value3', + }; + const caps = ['capability1', 'capability2', 'capability3']; + const filter = [0, 2]; + + const result = bidderData(wjsData, caps, filter); + + expect(result).to.deep.equal({ + capability1: 'value1', + capability3: 'value3', + }); + }); + + it('should return an empty object if the filter is empty', () => { + const wjsData = { + capability1: 'value1', + capability2: 'value2', + capability3: 'value3', + }; + const caps = ['capability1', 'capability3']; + const filter = []; + + const result = bidderData(wjsData, caps, filter); + + expect(result).to.deep.equal({}); + }); + }); + + describe('lowEntropyData', () => { + it('should return the correct low entropy data for Apple devices', () => { + const wjsData = { + complete_device_name: 'Apple iPhone X', + form_factor: 'Smartphone', + is_mobile: !0, + brand_name: 'Apple', + model_name: 'iPhone X', + }; + const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; + const expectedData = { + complete_device_name: 'Apple iPhone', + form_factor: 'Smartphone', + is_mobile: !0, + brand_name: 'Apple', + model_name: 'iPhone', + }; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + + it('should return the correct low entropy data for Android devices', () => { + const wjsData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const lowEntropyCaps = ['complete_device_name', 'form_factor', 'is_mobile']; + const expectedData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + + it('should return an empty object if the lowEntropyCaps array is empty', () => { + const wjsData = { + complete_device_name: 'Samsung SM-G981B (Galaxy S20 5G)', + form_factor: 'Smartphone', + is_mobile: !0, + }; + const lowEntropyCaps = []; + const expectedData = {}; + const result = lowEntropyData(wjsData, lowEntropyCaps); + expect(result).to.deep.equal(expectedData); + }); + }); + + describe('enrichBidderRequest', () => { + it('should enrich the bidder request with WURFL data', () => { + const reqBidsConfigObj = { + ortb2Fragments: { + global: { + device: {}, + }, + bidder: { + exampleBidder: { + device: { + ua: 'user-agent', + } + } + } + } + }; + const bidderCode = 'exampleBidder'; + const wjsData = { + capability1: 'value1', + capability2: 'value2' + }; + + enrichBidderRequest(reqBidsConfigObj, bidderCode, wjsData); + + expect(reqBidsConfigObj.ortb2Fragments.bidder).to.deep.equal({ + exampleBidder: { + device: { + ua: 'user-agent', + ext: { + wurfl: { + capability1: 'value1', + capability2: 'value2' + } + } + } + } + }); + }); + }); + + describe('makeOrtb2DeviceType', function () { + it('should return 1 when wurflData is_mobile and is_phone is true', function () { + const wurflData = { is_mobile: true, is_phone: true, is_tablet: false }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(1); + }); + + it('should return 1 when wurflData is_mobile and is_tablet is true', function () { + const wurflData = { is_mobile: true, is_phone: false, is_tablet: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(1); + }); + + it('should return 6 when wurflData is_mobile but is_phone and is_tablet are false', function () { + const wurflData = { is_mobile: true, is_phone: false, is_tablet: false }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(6); + }); + + it('should return 2 when wurflData is_full_desktop is true', function () { + const wurflData = { is_full_desktop: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(2); + }); + + it('should return 3 when wurflData is_connected_tv is true', function () { + const wurflData = { is_connected_tv: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(3); + }); + + it('should return 4 when wurflData is_phone is true and is_mobile is false or undefined', function () { + const wurflData = { is_phone: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(4); + }); + + it('should return 5 when wurflData is_tablet is true and is_mobile is false or undefined', function () { + const wurflData = { is_tablet: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(5); + }); + + it('should return 7 when wurflData is_ott is true', function () { + const wurflData = { is_ott: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.equal(7); + }); + + it('should return undefined when wurflData is_mobile is true but is_phone and is_tablet are missing', function () { + const wurflData = { is_mobile: true }; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.be.undefined; + }); + + it('should return undefined when no conditions are met', function () { + const wurflData = {}; + const result = makeOrtb2DeviceType(wurflData); + expect(result).to.be.undefined; + }); + }); +}); diff --git a/test/spec/modules/xeBidAdapter_spec.js b/test/spec/modules/xeBidAdapter_spec.js index 914b0cacd71..e15ab6251ea 100644 --- a/test/spec/modules/xeBidAdapter_spec.js +++ b/test/spec/modules/xeBidAdapter_spec.js @@ -1,8 +1,8 @@ -import { expect } from 'chai'; -import { config } from 'src/config.js'; -import { spec, getBidFloor } from 'modules/xeBidAdapter.js'; -import { deepClone } from 'src/utils'; -import { createEidsArray } from 'modules/userId/eids.js'; +import {expect} from 'chai'; +import {config} from 'src/config.js'; +import {spec} from 'modules/xeBidAdapter.js'; +import {deepClone} from 'src/utils'; +import {getBidFloor} from '../../../libraries/xeUtils/bidderUtils.js'; const ENDPOINT = 'https://pbjs.xe.works/bid'; @@ -10,7 +10,11 @@ const defaultRequest = { adUnitCode: 'test', bidId: '1', requestId: 'qwerty', - auctionId: 'auctionId', + ortb2: { + source: { + tid: 'auctionId' + } + }, ortb2Imp: { ext: { tid: 'tr1', @@ -27,7 +31,7 @@ const defaultRequest = { bidder: 'xe', params: { env: 'xe', - placement: 'test-banner', + pid: '40', ext: {} }, bidRequestsCount: 1 @@ -41,6 +45,17 @@ defaultRequestVideo.mediaTypes = { skipppable: true } }; + +const videoBidderRequest = { + bidderCode: 'xe', + bids: [{mediaTypes: {video: {}}, bidId: 'qwerty'}] +}; + +const displayBidderRequest = { + bidderCode: 'xe', + bids: [{bidId: 'qwerty'}] +}; + describe('xeBidAdapter', () => { describe('isBidRequestValid', function () { it('should return false when request params is missing', function () { @@ -55,9 +70,9 @@ describe('xeBidAdapter', () => { expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); - it('should return false when required placement param is missing', function () { + it('should return false when required pid param is missing', function () { const invalidRequest = deepClone(defaultRequest); - delete invalidRequest.params.placement; + delete invalidRequest.params.pid; expect(spec.isBidRequestValid(invalidRequest)).to.equal(false); }); @@ -80,7 +95,7 @@ describe('xeBidAdapter', () => { it('should send request with correct structure', function () { const request = spec.buildRequests([defaultRequest], {}); expect(request.method).to.equal('POST'); - expect(request.url).to.equal(ENDPOINT); + expect(request.url).to.equal(ENDPOINT + '/bid'); expect(request.options).to.have.property('contentType').and.to.equal('application/json'); expect(request).to.have.property('data'); }); @@ -88,22 +103,21 @@ describe('xeBidAdapter', () => { it('should build basic request structure', function () { const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; expect(request).to.have.property('bidId').and.to.equal(defaultRequest.bidId); - expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.auctionId); + expect(request).to.have.property('auctionId').and.to.equal(defaultRequest.ortb2.source.tid); expect(request).to.have.property('transactionId').and.to.equal(defaultRequest.ortb2Imp.ext.tid); expect(request).to.have.property('tz').and.to.equal(new Date().getTimezoneOffset()); expect(request).to.have.property('bc').and.to.equal(1); expect(request).to.have.property('floor').and.to.equal(null); - expect(request).to.have.property('banner').and.to.deep.equal({ sizes: [[300, 250], [300, 200]] }); + expect(request).to.have.property('banner').and.to.deep.equal({sizes: [[300, 250], [300, 200]]}); expect(request).to.have.property('gdprApplies').and.to.equal(0); expect(request).to.have.property('consentString').and.to.equal(''); expect(request).to.have.property('userEids').and.to.deep.equal([]); expect(request).to.have.property('usPrivacy').and.to.equal(''); - expect(request).to.have.property('coppa').and.to.equal(0); expect(request).to.have.property('sizes').and.to.deep.equal(['300x250', '300x200']); expect(request).to.have.property('ext').and.to.deep.equal({}); expect(request).to.have.property('env').and.to.deep.equal({ env: 'xe', - placement: 'test-banner' + pid: '40' }); expect(request).to.have.property('device').and.to.deep.equal({ ua: navigator.userAgent, @@ -186,7 +200,7 @@ describe('xeBidAdapter', () => { it('should build request with valid bidfloor', function () { const bfRequest = deepClone(defaultRequest); - bfRequest.getFloor = () => ({ floor: 5, currency: 'USD' }); + bfRequest.getFloor = () => ({floor: 5, currency: 'USD'}); const request = JSON.parse(spec.buildRequests([bfRequest], {}).data)[0]; expect(request).to.have.property('floor').and.to.equal(5); }); @@ -211,19 +225,11 @@ describe('xeBidAdapter', () => { expect(request).to.have.property('usPrivacy').and.equals('1YA-'); }); - it('should build request with coppa 1', function () { - config.setConfig({ - coppa: true - }); - const request = JSON.parse(spec.buildRequests([defaultRequest], {}).data)[0]; - expect(request).to.have.property('coppa').and.equals(1); - }); - it('should build request with extended ids', function () { const idRequest = deepClone(defaultRequest); idRequest.userIdAsEids = [ - { source: 'adserver.org', uids: [ { id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: { rtiPartner: 'TDID' } } ] }, - { source: 'pubcid.org', uids: [ { id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1 } ] } + {source: 'adserver.org', uids: [{id: 'TTD_ID_FROM_USER_ID_MODULE', atype: 1, ext: {rtiPartner: 'TDID'}}]}, + {source: 'pubcid.org', uids: [{id: 'pubCommonId_FROM_USER_ID_MODULE', atype: 1}]} ]; const request = JSON.parse(spec.buildRequests([idRequest], {}).data)[0]; expect(request).to.have.property('userEids').and.deep.equal(idRequest.userIdAsEids); @@ -263,19 +269,19 @@ describe('xeBidAdapter', () => { height: 250, ttl: 600, meta: { - advertiserDomains: ['xe.works'] + advertiserDomains: ['xe'] }, ext: { pixels: [ - [ 'iframe', 'surl1' ], - [ 'image', 'surl2' ], + ['iframe', 'surl1'], + ['image', 'surl2'], ] } }] } }; - const validResponse = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const validResponse = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); const bid = validResponse[0]; expect(validResponse).to.be.an('array').that.is.not.empty; expect(bid.requestId).to.equal('qwerty'); @@ -284,7 +290,7 @@ describe('xeBidAdapter', () => { expect(bid.width).to.equal(300); expect(bid.height).to.equal(250); expect(bid.ttl).to.equal(600); - expect(bid.meta).to.deep.equal({ advertiserDomains: ['xe.works'] }); + expect(bid.meta).to.deep.equal({advertiserDomains: ['xe']}); }); it('should interpret valid banner response', function () { @@ -305,7 +311,7 @@ describe('xeBidAdapter', () => { } }; - const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequest }); + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: displayBidderRequest}); const bid = validResponseBanner[0]; expect(validResponseBanner).to.be.an('array').that.is.not.empty; expect(bid.mediaType).to.equal('banner'); @@ -331,7 +337,7 @@ describe('xeBidAdapter', () => { } }; - const validResponseBanner = spec.interpretResponse(serverResponse, { bidderRequest: defaultRequestVideo }); + const validResponseBanner = spec.interpretResponse(serverResponse, {bidderRequest: videoBidderRequest}); const bid = validResponseBanner[0]; expect(validResponseBanner).to.be.an('array').that.is.not.empty; expect(bid.mediaType).to.equal('video'); @@ -358,8 +364,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -377,8 +383,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -396,8 +402,8 @@ describe('xeBidAdapter', () => { requestId: 'qwerty', ext: { pixels: [ - [ 'iframe', 'surl1?a=b' ], - [ 'image', 'surl2?a=b' ], + ['iframe', 'surl1?a=b'], + ['image', 'surl2?a=b'], ] } }] @@ -414,20 +420,20 @@ describe('xeBidAdapter', () => { describe('getBidFloor', function () { it('should return null when getFloor is not a function', () => { - const bid = { getFloor: 2 }; + 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 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: 'USD' }) + getFloor: () => ({floor: 'string', currency: 'USD'}) }; const result = getBidFloor(bid); expect(result).to.be.null; @@ -435,7 +441,7 @@ describe('xeBidAdapter', () => { it('should return null when currency is not USD', () => { const bid = { - getFloor: () => ({ floor: 5, currency: 'EUR' }) + getFloor: () => ({floor: 5, currency: 'EUR'}) }; const result = getBidFloor(bid); expect(result).to.be.null; @@ -443,7 +449,7 @@ describe('xeBidAdapter', () => { it('should return floor value when everything is correct', () => { const bid = { - getFloor: () => ({ floor: 5, currency: 'USD' }) + getFloor: () => ({floor: 5, currency: 'USD'}) }; const result = getBidFloor(bid); expect(result).to.equal(5); diff --git a/test/spec/modules/yahoosspBidAdapter_spec.js b/test/spec/modules/yahooAdsBidAdapter_spec.js similarity index 94% rename from test/spec/modules/yahoosspBidAdapter_spec.js rename to test/spec/modules/yahooAdsBidAdapter_spec.js index 40dc2b3c63b..f8ed7693f3f 100644 --- a/test/spec/modules/yahoosspBidAdapter_spec.js +++ b/test/spec/modules/yahooAdsBidAdapter_spec.js @@ -1,8 +1,9 @@ import { expect } from 'chai'; import { config } from 'src/config.js'; import { BANNER, VIDEO } from 'src/mediaTypes.js'; -import { spec } from 'modules/yahoosspBidAdapter.js'; +import { spec } from 'modules/yahooAdsBidAdapter.js'; import {createEidsArray} from '../../../modules/userId/eids'; +import {deepAccess} from '../../../src/utils'; const DEFAULT_BID_ID = '84ab500420319d'; const DEFAULT_BID_DCN = '2093845709823475'; @@ -193,7 +194,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { }); it('should define the correct bidder aliases', () => { - expect(spec.aliases).to.deep.equal(['yahoossp', 'yahooAdvertising']); + expect(spec.aliases).to.deep.equal([{ 'code': 'yahoossp', 'gvlid': 25 }, { 'code': 'yahooAdvertising', 'gvlid': 25 }]); }); it('should define the correct vendor ID', () => { @@ -1169,7 +1170,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); @@ -1201,7 +1203,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1298,7 +1301,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1351,7 +1355,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: undefined, playbackmethod: undefined, rewarded: undefined, - placement: undefined + placement: undefined, + plcmt: undefined }); }); }); @@ -1381,7 +1386,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: 123456, playbackmethod: 1, rewarded: 1, - placement: 1 + placement: 1, + plcmt: 1 } } } @@ -1410,7 +1416,8 @@ describe('Yahoo Advertising Bid Adapter:', () => { delivery: 1, pos: 123456, playbackmethod: 1, - placement: 1 + placement: 1, + plcmt: 1 } const validBidRequests = [bidRequest]; bidderRequest.bids = validBidRequests; @@ -1430,6 +1437,7 @@ describe('Yahoo Advertising Bid Adapter:', () => { pos: 123456, playbackmethod: 1, placement: 1, + plcmt: 1, rewarded: undefined }); }); @@ -1488,15 +1496,6 @@ describe('Yahoo Advertising Bid Adapter:', () => { expect(response[0].mediaType).to.equal('video'); }) - it('should insert video VAST win notification into vastUrl', () => { - const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); - const response = spec.interpretResponse(serverResponse, {bidderRequest}); - expect(response[0].ad).to.be.undefined; - expect(response[0].vastUrl).to.equal('https://yahoo.com?event=adAttempt'); - expect(response[0].vastXml).to.equal(''); - expect(response[0].mediaType).to.equal('video'); - }) - describe('wrapped in video players for display inventory', () => { beforeEach(() => { config.setConfig({ @@ -1621,5 +1620,74 @@ describe('Yahoo Advertising Bid Adapter:', () => { expect(response[0].bidderCode).to.be.undefined; }); }); + + describe('Renderer:', () => { + it('should create and set renderer when bidder request context is outstream, bidder request renderer is falsy, and bid response mediaType is VIDEO', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.not.be.undefined; + }); + + it('should not create and set renderer when bidder request renderer is falsy and bid response mediaType is VIDEO, but bidder request context is not outstream,', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.not.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + + it('should not create and set renderer when bidder request context is outstream and bid response mediaType is VIDEO, but bidder request renderer is not falsy', () => { + config.setConfig({ + yahooAds: { + mode: VIDEO + } + }); + const { serverResponse, bidderRequest } = generateResponseMock('video', 'vast'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + bidderRequest.renderer = 'not falsy'; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.not.be.undefined; + expect(response[0].mediaType).to.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + + it('should not create and set renderer when bidder request context is outstream and bidder request renderer is falsy, but bid response mediaType is not VIDEO', () => { + const { serverResponse, bidderRequest } = generateResponseMock('banner'); + bidderRequest.mediaTypes = { + video: { + context: 'outstream' + } + }; + const response = spec.interpretResponse(serverResponse, {bidderRequest}); + expect(deepAccess(bidderRequest, 'mediaTypes.video.context')).to.equal('outstream'); + expect(bidderRequest.renderer).to.be.undefined; + expect(response[0].mediaType).to.not.equal('video'); + expect(response[0].renderer).to.be.undefined; + }); + }); }); }); diff --git a/test/spec/modules/yandexAnalyticsAdapter_spec.js b/test/spec/modules/yandexAnalyticsAdapter_spec.js index ca9b29d13a5..a7a850b7cf2 100644 --- a/test/spec/modules/yandexAnalyticsAdapter_spec.js +++ b/test/spec/modules/yandexAnalyticsAdapter_spec.js @@ -1,7 +1,8 @@ import * as sinon from 'sinon'; -import yandexAnalytics, { EVENTS_TO_TRACK } from 'modules/yandexAnalyticsAdapter.js'; +import yandexAnalytics, { EVENTS_TO_TRACK, PBJS_INIT_EVENT_NAME } from 'modules/yandexAnalyticsAdapter.js'; import * as log from '../../../src/utils.js' import * as events from '../../../src/events.js'; +import * as globalUtils from '../../../src/prebidGlobal.js'; describe('Yandex analytics adapter testing', () => { const sandbox = sinon.createSandbox(); @@ -11,6 +12,13 @@ describe('Yandex analytics adapter testing', () => { let onEvent; const counterId = 123; const counterWindowKey = 'yaCounter123'; + const prebidVersion = '123.0'; + const prebidInitEvent = { + event: PBJS_INIT_EVENT_NAME, + data: { + version: prebidVersion, + }, + } beforeEach(() => { yandexAnalytics.counters = {}; @@ -19,6 +27,9 @@ describe('Yandex analytics adapter testing', () => { yandexAnalytics.oneCounterInited = false; clock = sinon.useFakeTimers(); logError = sandbox.stub(log, 'logError'); + sandbox.stub(globalUtils, 'getGlobal').returns({ + version: prebidVersion, + }); sandbox.stub(log, 'logInfo'); getEvents = sandbox.stub(events, 'getEvents').returns([]); onEvent = sandbox.stub(events, 'on'); @@ -86,12 +97,15 @@ describe('Yandex analytics adapter testing', () => { eventType: 'Some_untracked_event', } ]); - const eventsToSend = [{ - event: EVENTS_TO_TRACK[0], - data: { - eventType: EVENTS_TO_TRACK[0], + const eventsToSend = [ + prebidInitEvent, + { + event: EVENTS_TO_TRACK[0], + data: { + eventType: EVENTS_TO_TRACK[0], + } } - }]; + ]; yandexAnalytics.enableAnalytics({ options: { @@ -139,9 +153,12 @@ describe('Yandex analytics adapter testing', () => { clock.tick(2001); const [ sentEvents ] = counterPbjsMethod.getCall(0).args; - chai.expect(sentEvents).to.deep.equal([{ - event, - data: {}, - }]); + chai.expect(sentEvents).to.deep.equal([ + prebidInitEvent, + { + event, + data: {}, + } + ]); }); }); diff --git a/test/spec/modules/yandexIdSystem_spec.js b/test/spec/modules/yandexIdSystem_spec.js new file mode 100644 index 00000000000..593ce0841d4 --- /dev/null +++ b/test/spec/modules/yandexIdSystem_spec.js @@ -0,0 +1,201 @@ +// @ts-check + +import { + BIDDER_EID_KEY, + YANDEX_ID_KEY, + YANDEX_EXT_COOKIE_NAMES, + BIDDER_CODE, + YANDEX_USER_ID_KEY, + YANDEX_STORAGE_TYPE, + YANDEX_MIN_EXPIRE_DAYS, + PREBID_STORAGE, + yandexIdSubmodule, +} from '../../../modules/yandexIdSystem.js'; +import {attachIdSystem} from '../../../modules/userId/index.js'; +import {createEidsArray} from '../../../modules/userId/eids.js'; +import {createSandbox} from 'sinon' +import * as utils from '../../../src/utils.js'; + +/** + * @typedef {import('sinon').SinonStub} SinonStub + * @typedef {import('sinon').SinonSpy} SinonSpy + * @typedef {import('sinon').SinonSandbox} SinonSandbox + */ + +const MIN_METRICA_ID_LEN = 17; + +/** @satisfies {import('../../../modules/userId/index.js').SubmoduleConfig} */ +const CORRECT_SUBMODULE_CONFIG = { + name: BIDDER_CODE, + storage: { + expires: YANDEX_MIN_EXPIRE_DAYS, + name: YANDEX_USER_ID_KEY, + type: YANDEX_STORAGE_TYPE, + refreshInSeconds: undefined, + }, + params: undefined, + value: undefined, +}; + +/** @type {import('../../../modules/userId/index.js').SubmoduleConfig[]} */ +const INCORRECT_SUBMODULE_CONFIGS = [ + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + expires: 0, + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + type: 'html5' + } + }, + { + ...CORRECT_SUBMODULE_CONFIG, + storage: { + ...CORRECT_SUBMODULE_CONFIG.storage, + name: 'custom_key' + } + }, +]; + +const YANDEX_EXT_COOKIE_VALUE = 'ext_cookie_value'; + +describe('YandexId module', () => { + /** @type {SinonSandbox} */ + let sandbox; + /** @type {SinonStub} */ + let getCryptoRandomValuesStub; + /** @type {SinonStub} */ + let randomStub; + /** @type {SinonStub} */ + let getCookieStub; + /** @type {SinonStub} */ + let cookiesAreEnabledStub; + /** @type {SinonSpy} */ + let logErrorSpy; + + beforeEach(() => { + sandbox = createSandbox(); + getCookieStub = sandbox.stub(PREBID_STORAGE, 'getCookie').returns(YANDEX_EXT_COOKIE_VALUE); + cookiesAreEnabledStub = sandbox.stub(PREBID_STORAGE, 'cookiesAreEnabled'); + logErrorSpy = sandbox.spy(utils, 'logError'); + + getCryptoRandomValuesStub = sandbox + .stub(window.crypto, 'getRandomValues') + .callsFake((bufferView) => { + if (bufferView != null) { + bufferView[0] = 10000; + } + + return null; + }); + randomStub = sandbox.stub(window.Math, 'random').returns(0.555); + }); + + afterEach(() => { + sandbox.restore(); + }); + + describe('getId()', () => { + it('user id matches Yandex Metrica format', () => { + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG)?.id; + + expect(isNaN(Number(generatedId))).to.be.false; + expect(generatedId).to.have.length.greaterThanOrEqual( + MIN_METRICA_ID_LEN + ); + }); + + it('uses stored id', () => { + const storedId = '11111111111111111'; + const generatedId = yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG, undefined, storedId)?.id; + + expect(generatedId).to.be.equal(storedId); + }) + + describe('config validation', () => { + INCORRECT_SUBMODULE_CONFIGS.forEach((config, i) => { + it(`invalid config #${i} fails`, () => { + const generatedId = yandexIdSubmodule.getId(config)?.id; + + expect(generatedId).to.be.undefined; + expect(logErrorSpy.called).to.be.true; + }) + }) + }) + + describe('crypto', () => { + it('uses Math.random when crypto is not available', () => { + const cryptoTmp = window.crypto; + + // @ts-expect-error -- Crypto is always defined in modern JS. TS yells when trying to delete non-nullable property. + delete window.crypto; + + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); + + expect(randomStub.calledOnce).to.be.true; + expect(getCryptoRandomValuesStub.called).to.be.false; + + window.crypto = cryptoTmp; + }); + + it('uses crypto when it is available', () => { + yandexIdSubmodule.getId(CORRECT_SUBMODULE_CONFIG); + + expect(randomStub.called).to.be.false; + expect(getCryptoRandomValuesStub.calledOnce).to.be.true; + }); + }); + }); + + describe('decode()', () => { + it('should not transform value', () => { + const value = 'test value'; + + expect(yandexIdSubmodule.decode(value).yandexId).to.equal(value); + }); + }); + + describe('eid', () => { + const id = '11111111111111111'; + const userId = { [YANDEX_ID_KEY]: id }; + + before(() => { + attachIdSystem(yandexIdSubmodule); + }); + + it('with enabled cookies', () => { + cookiesAreEnabledStub.returns(true); + const [eid] = createEidsArray(userId); + + expect(eid).to.deep.equal({ + source: BIDDER_EID_KEY, + uids: [{ + id, + atype: 1, + ext: YANDEX_EXT_COOKIE_NAMES.reduce((acc, cookieName) => ({ + ...acc, + [cookieName]: YANDEX_EXT_COOKIE_VALUE, + }), {}), + }] + }); + }); + + it('with disabled cookies', () => { + cookiesAreEnabledStub.returns(false); + const [eid] = createEidsArray(userId); + + expect(eid).to.deep.equal({ + source: BIDDER_EID_KEY, + uids: [{ + id, + atype: 1, + }] + }); + }); + }); +}); diff --git a/test/spec/modules/yieldlabBidAdapter_spec.js b/test/spec/modules/yieldlabBidAdapter_spec.js index 225186dd326..25d8f2baec0 100644 --- a/test/spec/modules/yieldlabBidAdapter_spec.js +++ b/test/spec/modules/yieldlabBidAdapter_spec.js @@ -682,7 +682,7 @@ describe('yieldlabBidAdapter', () => { expect(result[0].netRevenue).to.equal(false); expect(result[0].ttl).to.equal(300); expect(result[0].referrer).to.equal(''); - expect(result[0].meta.advertiserDomains).to.equal('yieldlab'); + expect(result[0].meta.advertiserDomains).to.deep.equal(['yieldlab']); expect(result[0].ad).to.include('" }); adResponse.ad = ""; - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); - assert.ok(doc.close.called, 'close method called'); + return renderAd(doc, bidId).then(() => { + assert.ok(doc.write.calledWith(adResponse.ad), 'ad was written to doc'); + assert.ok(doc.close.called, 'close method called'); + }) }); it('should place the url inside an iframe on the doc', function () { pushBidResponseToAuction({ adUrl: 'http://server.example.com/ad/ad.js' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(doc.createElement, 'iframe'); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(doc.createElement, 'iframe'); + }); }); it('should log an error when no ad or url', function () { pushBidResponseToAuction({}); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.called(spyLogError); + return renderAd(doc, bidId).then(() => { + sinon.assert.called(spyLogError); + }); }); it('should log an error when not in an iFrame', function () { @@ -1287,17 +1325,29 @@ describe('Unit: Prebid Module', function () { ad: "" }); inIframe = false; - $$PREBID_GLOBAL$$.renderAd(document, bidId); - const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(document, bidId).then(() => { + const error = `Error rendering ad (id: ${bidId}): renderAd was prevented from writing to the main document.`; + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); + }); + + it('should emit AD_RENDER_SUCCEEDED', () => { + sandbox.stub(events, 'emit'); + pushBidResponseToAuction({ + ad: "" + }); + return renderAd(document, bidId).then(() => { + sinon.assert.calledWith(events.emit, EVENTS.AD_RENDER_SUCCEEDED, sinon.match({adId: bidId})); + }); }); it('should not render videos', function () { pushBidResponseToAuction({ mediatype: 'video' }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.notCalled(doc.write); + return renderAd(doc, bidId).then(() => { + sinon.assert.notCalled(doc.write); + }); }); it('should catch errors thrown when trying to write ads to the page', function () { @@ -1307,25 +1357,28 @@ describe('Unit: Prebid Module', function () { var error = { message: 'doc write error' }; doc.write = sinon.stub().throws(error); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` - assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + return renderAd(doc, bidId).then(() => { + var errorMessage = `Error rendering ad (id: ${bidId}): doc write error` + assert.ok(spyLogError.calledWith(errorMessage), 'expected error was logged'); + }); }); it('should log an error when ad not found', function () { var fakeId = 99; - $$PREBID_GLOBAL$$.renderAd(doc, fakeId); - var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` - assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + return renderAd(doc, fakeId).then(() => { + var error = `Error rendering ad (id: ${fakeId}): Cannot find ad '${fakeId}'` + assert.ok(spyLogError.calledWith(error), 'expected error was logged'); + }); }); it('should save bid displayed to winning bid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + return renderAd(doc, bidId).then(() => { + assert.deepEqual($$PREBID_GLOBAL$$.getAllWinningBids()[0], adResponse); + }); }); it('fires billing url if present on s2s bid', function () { @@ -1336,22 +1389,23 @@ describe('Unit: Prebid Module', function () { burl }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - - sinon.assert.calledOnce(triggerPixelStub); - sinon.assert.calledWith(triggerPixelStub, burl); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledOnce(triggerPixelStub); + sinon.assert.calledWith(triggerPixelStub, burl); + }); }); it('should call addWinningBid', function () { pushBidResponseToAuction({ ad: "" }); - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - var message = 'Calling renderAd with adId :' + bidId; - sinon.assert.calledWith(spyLogMessage, message); + return renderAd(doc, bidId).then(() => { + var message = 'Calling renderAd with adId :' + bidId; + sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + }); }); it('should warn stale rendering', function () { @@ -1368,38 +1422,36 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); - - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); - - // Second render should have a warning but still added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); - - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.calledWith(onStaleEvent, adResponse); - - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); + + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); + doc.write.resetHistory(); + return renderAd(doc, bidId); + }).then(() => { + // Second render should have a warning but still be rendered + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.calledWith(onStaleEvent, adResponse); + sinon.assert.called(doc.write); + + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + }); }); it('should stop stale rendering', function () { @@ -1419,38 +1471,40 @@ describe('Unit: Prebid Module', function () { }); // First render should pass with no warning and added to winning bids - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.neverCalledWith(spyLogWarn, warning); + return renderAd(doc, bidId).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - sinon.assert.calledWith(onWonEvent, adResponse); - sinon.assert.notCalled(onStaleEvent); + sinon.assert.calledWith(onWonEvent, adResponse); + sinon.assert.notCalled(onStaleEvent); - // Reset call history for spies and stubs - spyLogMessage.resetHistory(); - spyLogWarn.resetHistory(); - spyAddWinningBid.resetHistory(); - onWonEvent.resetHistory(); - onStaleEvent.resetHistory(); + // Reset call history for spies and stubs + spyLogMessage.resetHistory(); + spyLogWarn.resetHistory(); + spyAddWinningBid.resetHistory(); + onWonEvent.resetHistory(); + onStaleEvent.resetHistory(); - // Second render should have a warning and do not proceed further - $$PREBID_GLOBAL$$.renderAd(doc, bidId); - sinon.assert.calledWith(spyLogMessage, message); - sinon.assert.calledWith(spyLogWarn, warning); + // Second render should have a warning and do not proceed further + return renderAd(doc, bidId); + }).then(() => { + sinon.assert.calledWith(spyLogMessage, message); + sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.notCalled(onWonEvent); - sinon.assert.calledWith(onStaleEvent, adResponse); + sinon.assert.notCalled(onWonEvent); + sinon.assert.calledWith(onStaleEvent, adResponse); - // Clean up - $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); - $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); - configObj.setConfig({'auctionOptions': {}}); + // Clean up + $$PREBID_GLOBAL$$.offEvent(EVENTS.BID_WON, onWonEvent); + $$PREBID_GLOBAL$$.offEvent(EVENTS.STALE_RENDER, onStaleEvent); + configObj.setConfig({'auctionOptions': {}}); + }); }); }); @@ -1811,7 +1865,24 @@ describe('Unit: Prebid Module', function () { adUnitCodes: 'two' }); sinon.assert.calledWith(startAuctionStub, sinon.match({ - adUnits: [{code: 'two'}] + adUnits: [{code: 'two'}], + adUnitCodes: ['two'] + })); + }); + + it('does not repeat ad unit codes on twin ad units', () => { + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [{code: 'au1'}, {code: 'au2'}, {code: 'au1'}, {code: 'au2'}], + }); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + adUnitCodes: ['au1', 'au2'] + })); + }); + + it('filters out repeated ad unit codes from input', () => { + $$PREBID_GLOBAL$$.requestBids({adUnitCodes: ['au1', 'au1', 'au2']}); + sinon.assert.calledWith(startAuctionStub, sinon.match({ + adUnitCodes: ['au1', 'au2'] })); }); @@ -1867,7 +1938,7 @@ describe('Unit: Prebid Module', function () { }); it('passes ortb2 fragments to createAuction', () => { - const ortb2Fragments = {}; + const ortb2Fragments = {global: {}, bidder: {}}; pbjsModule.startAuction({ adUnits: [{ code: 'au', @@ -2475,6 +2546,52 @@ describe('Unit: Prebid Module', function () { } }); + if (FEATURES.NATIVE) { + Object.entries({ + missing: {}, + negative: {id: -1}, + 'not an integer': {id: 1.23}, + NaN: {id: 'garbage'} + }).forEach(([t, props]) => { + it(`should reject native ortb when asset ID is ${t}`, () => { + const adUnit = { + code: 'au', + mediaTypes: { + native: { + ortb: { + assets: [props] + } + } + }, + bids: [{bidder: 'appnexus'}] + }; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [adUnit] + }); + expect(auctionArgs.adUnits[0].bids.length).to.equal(0); + }); + }); + + ['sendTargetingKeys', 'types'].forEach(key => { + it(`should reject native that includes both ortb and ${key}`, () => { + const adUnit = { + code: 'au', + mediaTypes: { + native: { + ortb: {}, + [key]: {} + } + }, + bids: [{bidder: 'appnexus'}] + }; + $$PREBID_GLOBAL$$.requestBids({ + adUnits: [adUnit] + }); + expect(auctionArgs.adUnits[0].bids.length).to.equal(0); + }) + }); + } + it('should throw error message and remove adUnit if adUnit.bids is not defined correctly', function () { const adUnits = [{ code: 'ad-unit-1', @@ -2765,7 +2882,7 @@ describe('Unit: Prebid Module', function () { let result = targeting.getAllTargeting(['/19968336/header-bid-tag-0', '/19968336/header-bid-tag1']); // $$PREBID_GLOBAL$$.getAdserverTargeting(); let expected = { '/19968336/header-bid-tag-0': { - 'foobar': '0x0,300x250,300x600', + 'foobar': '300x250,300x600,0x0', [TARGETING_KEYS.SIZE]: '300x250', [TARGETING_KEYS.PRICE_BUCKET]: '10.00', [TARGETING_KEYS.AD_ID]: '233bcbee889d46d', @@ -3436,7 +3553,7 @@ describe('Unit: Prebid Module', function () { if (FEATURES.VIDEO) { describe('markWinningBidAsUsed', function () { const adUnitCode = '/19968336/header-bid-tag-0'; - let winningBid; + let winningBid, markedBid; beforeEach(() => { const bidsReceived = $$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode); @@ -3445,20 +3562,56 @@ describe('Unit: Prebid Module', function () { // mark the bid and verify the state has changed to RENDERED winningBid = targeting.getWinningBids(adUnitCode)[0]; auction.getAuctionId = function() { return winningBid.auctionId }; + sandbox.stub(events, 'emit'); + markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, + bid => bid.adId === winningBid.adId); }) afterEach(() => { resetAuction(); }) - it('marks the bid object as used for the given adUnitCode/adId combination', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - + function checkBidRendered() { expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); + } + + Object.entries({ + 'analytics=true': { + mark(options = {}) { + $$PREBID_GLOBAL$$.markWinningBidAsUsed(Object.assign({analytics: true}, options)) + }, + checkBidWon() { + sinon.assert.calledWith(events.emit, EVENTS.BID_WON, markedBid); + } + }, + 'analytics=false': { + mark(options = {}) { + $$PREBID_GLOBAL$$.markWinningBidAsUsed(options) + }, + checkBidWon() { + sinon.assert.notCalled(events.emit) + } + } + }).forEach(([t, {mark, checkBidWon}]) => { + describe(`when ${t}`, () => { + it('marks the bid object as used for the given adUnitCode/adId combination', function () { + mark({ adUnitCode, adId: winningBid.adId }); + checkBidRendered(); + checkBidWon(); + }); + it('marks the winning bid object as used for the given adUnitCode', function () { + mark({ adUnitCode }); + checkBidRendered(); + checkBidWon(); + }); + + it('marks a bid object as used for the given adId', function () { + mark({ adId: winningBid.adId }); + checkBidRendered(); + checkBidWon(); + }); + }) + }) it('try and mark the bid object, but fail because we supplied the wrong adId', function () { $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode, adId: 'miss' }); @@ -3467,24 +3620,6 @@ describe('Unit: Prebid Module', function () { expect(markedBid.status).to.not.equal(BID_STATUS.RENDERED); }); - - it('marks the winning bid object as used for the given adUnitCode', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adUnitCode }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); - - it('marks a bid object as used for the given adId', function () { - // make sure the auction has "state" and does not reload the fixtures - $$PREBID_GLOBAL$$.markWinningBidAsUsed({ adId: winningBid.adId }); - const markedBid = find($$PREBID_GLOBAL$$.getBidResponsesForAdUnitCode(adUnitCode).bids, - bid => bid.adId === winningBid.adId); - - expect(markedBid.status).to.equal(BID_STATUS.RENDERED); - }); }); } @@ -3616,67 +3751,106 @@ describe('Unit: Prebid Module', function () { auctionManagerStub.returns(bidsReceived) let bids = $$PREBID_GLOBAL$$.getAllPrebidWinningBids(); - expect(bids.length).to.equal(1); + expect(bids.length).to.equal(1); sandbox expect(bids[0].adId).to.equal('adid-1'); }); }); describe('deferred billing', function () { - const sandbox = sinon.createSandbox(); - - let adUnits = [ - { - code: 'adUnit-code-1', - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - adUnitId: '1234567890', - bids: [ - { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1' } - ] - }, - { - code: 'adUnit-code-2', - deferBilling: true, - mediaTypes: { banner: { sizes: [[300, 250], [300, 600]] } }, - adUnitId: '0987654321', - bids: [ - { bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2' } - ] - } - ]; - - let winningBid1 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' } - let winningBid2 = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-2', adUnitId: '0987654321' } - let adUnitCodes = ['adUnit-code-1', 'adUnit-code-2']; - let auction = auctionModule.newAuction({adUnits, adUnitCodes, callback: function() {}, cbTimeout: 2000}); + let bid; beforeEach(function () { - sandbox.spy(adapterManager, 'callBidWonBidder'); - sandbox.spy(adapterManager, 'callBidBillableBidder'); - sandbox.stub(auctionManager, 'getBidsReceived').returns([winningBid1]); - }); - - afterEach(function () { - sandbox.resetHistory(); - sandbox.restore(); + bid = { adapterCode: 'pubmatic', bidder: 'pubmatic', params: {placementId: '10433394'}, adUnitCode: 'adUnit-code-1', adUnitId: '1234567890', adId: 'abcdefg' }; + sandbox.spy(adapterManager, 'triggerBilling'); + sandbox.stub(auctionManager, 'getAllWinningBids').returns([bid]); }); - it('should by default invoke callBidWonBidder and callBidBillableBidder', function () { - auction.addWinningBid(winningBid1); - sinon.assert.calledOnce(adapterManager.callBidWonBidder); - sinon.assert.calledOnce(adapterManager.callBidBillableBidder); - }); + Object.entries({ + 'bid': () => bid, + 'adUnitCode': () => ({adUnitCode: bid.adUnitCode}) + }).forEach(([t, val]) => { + it(`should trigger billing when invoked with ${t}`, () => { + $$PREBID_GLOBAL$$.triggerBilling(val()); + sinon.assert.calledWith(adapterManager.triggerBilling, bid); + }) + }) + }); - it('should only invoke callBidWonBidder and NOT callBidBillableBidder if deferBilling is present and true within the winning adUnit object', function () { - auction.addWinningBid(winningBid2); - sinon.assert.calledOnce(adapterManager.callBidWonBidder); - sinon.assert.notCalled(adapterManager.callBidBillableBidder); + describe('clearAllAuctions', () => { + after(() => { + resetAuction(); }); - - it('should invoke callBidBillableBidder when pbjs.triggerBilling is invoked', function () { - $$PREBID_GLOBAL$$.triggerBilling(winningBid1); - sinon.assert.calledOnce(auctionManager.getBidsReceived); - sinon.assert.notCalled(adapterManager.callBidWonBidder); - sinon.assert.calledOnce(adapterManager.callBidBillableBidder); + it('clears auction data', function () { + expect(auctionManager.getBidsReceived().length).to.not.equal(0); + $$PREBID_GLOBAL$$.clearAllAuctions(); + expect(auctionManager.getBidsReceived().length).to.equal(0); }); }); + + describe('setBattrForAdUnit', () => { + it('should set copy battr to both places', () => { + const adUnit = { + ortb2Imp: { + video: { + battr: 'banned attribute' + } + }, + mediaTypes: { + video: {} + } + } + + setBattrForAdUnit(adUnit, 'video'); + + expect(adUnit.mediaTypes.video.battr).to.deep.equal('banned attribute'); + expect(adUnit.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + + const adUnit2 = { + mediaTypes: { + video: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + video: {} + } + } + + setBattrForAdUnit(adUnit2, 'video'); + + expect(adUnit2.ortb2Imp.video.battr).to.deep.equal('banned attribute'); + expect(adUnit2.mediaTypes.video.battr).to.deep.equal('banned attribute'); + }) + + it('should log warn if both are specified and differ from eachother', () => { + let spyLogWarn = sinon.spy(utils, 'logWarn'); + const adUnit = { + mediaTypes: { + native: { + battr: 'banned attribute' + } + }, + ortb2Imp: { + native: { + battr: 'banned attribute 2' + } + } + } + setBattrForAdUnit(adUnit, 'native'); + sinon.assert.calledOnce(spyLogWarn); + spyLogWarn.resetHistory(); + utils.logWarn.restore(); + }) + + it('should not copy for undefined battr', () => { + const adUnit = { + mediaTypes: { + native: {} + } + } + setBattrForAdUnit(adUnit, 'native'); + expect(adUnit.mediaTypes.native).to.deep.equal({}); + expect(adUnit.mediaTypes.ortb2Imp).to.not.exist; + }) + }) }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 189066f7f88..aeb78501d51 100644 --- a/test/spec/unit/secureCreatives_spec.js +++ b/test/spec/unit/secureCreatives_spec.js @@ -13,11 +13,23 @@ import 'modules/nativeRendering.js'; import {expect} from 'chai'; -import { AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS } from 'src/constants.js'; +import {AD_RENDER_FAILED_REASON, BID_STATUS, EVENTS} from 'src/constants.js'; +import {getBidToRender} from '../../../src/adRendering.js'; describe('secureCreatives', () => { let sandbox; + function getBidToRenderHook(next, adId) { + // make sure that bids can be retrieved asynchronously + next(adId, new Promise((resolve) => setTimeout(resolve))) + } + before(() => { + getBidToRender.before(getBidToRenderHook); + }); + after(() => { + getBidToRender.getHooks({hook: getBidToRenderHook}).remove() + }); + beforeEach(() => { sandbox = sinon.sandbox.create(); }); @@ -30,6 +42,10 @@ describe('secureCreatives', () => { return Object.assign({origin: 'mock-origin', ports: []}, ev) } + function receive(ev) { + return Promise.resolve(receiveMessage(ev)); + } + describe('getReplier', () => { it('should use source.postMessage if no MessagePort is available', () => { const ev = { @@ -153,17 +169,17 @@ describe('secureCreatives', () => { data: JSON.stringify(data), }); - receiveMessage(ev); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + }); }); it('should allow stale rendering without config', function () { @@ -180,29 +196,23 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + resetHistories(adResponse.renderer.render); + return receive(ev); + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + }); }); it('should stop stale rendering with config', function () { @@ -221,29 +231,27 @@ describe('secureCreatives', () => { data: JSON.stringify(data) }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.calledWith(spyAddWinningBid, adResponse); - sinon.assert.calledOnce(adResponse.renderer.render); - sinon.assert.calledWith(adResponse.renderer.render, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); - - expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); - - resetHistories(adResponse.renderer.render); - - receiveMessage(ev); - - sinon.assert.calledWith(spyLogWarn, warning); - sinon.assert.notCalled(spyAddWinningBid); - sinon.assert.notCalled(adResponse.renderer.render); - sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); - - configObj.setConfig({'auctionOptions': {}}); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.calledWith(spyAddWinningBid, adResponse); + sinon.assert.calledOnce(adResponse.renderer.render); + sinon.assert.calledWith(adResponse.renderer.render, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + + expect(adResponse).to.have.property('status', BID_STATUS.RENDERED); + + resetHistories(adResponse.renderer.render); + return receive(ev) + }).then(() => { + sinon.assert.calledWith(spyLogWarn, warning); + sinon.assert.notCalled(spyAddWinningBid); + sinon.assert.notCalled(adResponse.renderer.render); + sinon.assert.neverCalledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledWith(stubEmit, EVENTS.STALE_RENDER, adResponse); + configObj.setConfig({'auctionOptions': {}}); + }); }); it('should emit AD_RENDER_FAILED if requested missing adId', () => { @@ -253,11 +261,12 @@ describe('secureCreatives', () => { adId: 'missing' }) }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, - adId: 'missing' - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.CANNOT_FIND_AD, + adId: 'missing' + })); + }); }); it('should emit AD_RENDER_FAILED if creative can\'t be sent to rendering frame', () => { @@ -271,11 +280,12 @@ describe('secureCreatives', () => { adId: bidId }) }); - receiveMessage(ev) - sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ - reason: AD_RENDER_FAILED_REASON.EXCEPTION, - adId: bidId - })); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.AD_RENDER_FAILED, sinon.match({ + reason: AD_RENDER_FAILED_REASON.EXCEPTION, + adId: bidId + })); + }) }); it('should include renderers in responses', () => { @@ -287,8 +297,9 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }); - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => JSON.parse(ob).renderer === 'mock-renderer')); + }); }); if (FEATURES.NATIVE) { @@ -318,23 +329,24 @@ describe('secureCreatives', () => { }, data: JSON.stringify({adId: bidId, message: 'Prebid Request'}) }) - receiveMessage(ev); - sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { - const data = JSON.parse(ob); - ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); - const native = data.native; - sinon.assert.match(native, { - ortb: bid.native.ortb, - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - }) - expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ - adTemplate: bid.native.adTemplate, - rendererUrl: bid.native.rendererUrl, - body: 'vbody' - }); - return true; - })) + return receive(ev).then(() => { + sinon.assert.calledWith(ev.source.postMessage, sinon.match(ob => { + const data = JSON.parse(ob); + ['width', 'height'].forEach(prop => expect(data[prop]).to.not.exist); + const native = data.native; + sinon.assert.match(native, { + ortb: bid.native.ortb, + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + }) + expect(Object.fromEntries(native.assets.map(({key, value}) => [key, value]))).to.eql({ + adTemplate: bid.native.adTemplate, + rendererUrl: bid.native.rendererUrl, + body: 'vbody' + }); + return true; + })) + }); }) } }); @@ -361,16 +373,16 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - - sinon.assert.neverCalledWith(spyLogWarn, warning); - sinon.assert.calledOnce(stubGetAllAssetsMessage); - sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); - sinon.assert.calledOnce(ev.source.postMessage); - sinon.assert.notCalled(stubFireNativeTrackers); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - sinon.assert.calledOnce(spyAddWinningBid); - sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + return receive(ev).then(() => { + sinon.assert.neverCalledWith(spyLogWarn, warning); + sinon.assert.calledOnce(stubGetAllAssetsMessage); + sinon.assert.calledWith(stubGetAllAssetsMessage, data, adResponse); + sinon.assert.calledOnce(ev.source.postMessage); + sinon.assert.notCalled(stubFireNativeTrackers); + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + sinon.assert.calledOnce(spyAddWinningBid); + sinon.assert.neverCalledWith(stubEmit, EVENTS.STALE_RENDER); + }); }); it('Prebid native should not fire BID_WON when receiveMessage is called more than once', () => { @@ -391,12 +403,75 @@ describe('secureCreatives', () => { origin: 'any origin' }); - receiveMessage(ev); - sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); - - receiveMessage(ev); - stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + return receive(ev); + }).then(() => { + stubEmit.withArgs(EVENTS.BID_WON, adResponse).calledOnce; + }); }); + + it('should fire BID_WON when no asset is requested', () => { + pushBidResponseToAuction({}); + const data = { + adId: bidId, + message: 'Prebid Native', + }; + + const ev = makeEvent({ + data: JSON.stringify(data), + }); + return receive(ev).then(() => { + sinon.assert.calledWith(stubEmit, EVENTS.BID_WON, adResponse); + }); + }) + + describe('resizing', () => { + let container, slot; + before(() => { + const [gtag, atag] = [window.googletag, window.apntag]; + delete window.googletag; + delete window.apntag; + after(() => { + window.googletag = gtag; + window.apntag = atag; + }) + }) + beforeEach(() => { + pushBidResponseToAuction({ + adUnitCode: 'mock-au' + }); + container = document.createElement('div'); + container.id = 'mock-au'; + slot = document.createElement('div'); + container.appendChild(slot); + document.body.appendChild(container) + }); + afterEach(() => { + if (container) { + document.body.removeChild(container); + } + }) + it('should handle resize request', () => { + const ev = makeEvent({ + data: JSON.stringify({ + adId: bidId, + message: 'Prebid Native', + action: 'resizeNativeHeight', + width: 123, + height: 321 + }), + source: { + postMessage: sinon.stub() + }, + origin: 'any origin' + }); + return receive(ev).then(() => { + expect(slot.style.width).to.eql('123px'); + expect(slot.style.height).to.eql('321px'); + }); + }) + }) }); describe('Prebid Event', () => { @@ -422,13 +497,14 @@ describe('secureCreatives', () => { }, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { - adId: bidId, - bid: adResponse, - reason: 'Fail reason', - message: 'Fail message' - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_FAILED, { + adId: bidId, + bid: adResponse, + reason: 'Fail reason', + message: 'Fail message' + })).to.equal(shouldEmit); + }); }); it(`should${shouldEmit ? ' ' : ' not '}emit AD_RENDER_SUCCEEDED`, () => { @@ -439,12 +515,13 @@ describe('secureCreatives', () => { adId: bidId, }) }); - receiveMessage(event); - expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { - adId: bidId, - bid: adResponse, - doc: null - })).to.equal(shouldEmit); + return receive(event).then(() => { + expect(stubEmit.calledWith(EVENTS.AD_RENDER_SUCCEEDED, { + adId: bidId, + bid: adResponse, + doc: null + })).to.equal(shouldEmit); + }); }); }); }); diff --git a/test/spec/unit/utils/focusTimeout_spec.js b/test/spec/unit/utils/focusTimeout_spec.js new file mode 100644 index 00000000000..3fcc4af18fe --- /dev/null +++ b/test/spec/unit/utils/focusTimeout_spec.js @@ -0,0 +1,87 @@ +import {setFocusTimeout, reset} from '../../../../src/utils/focusTimeout'; + +export const setDocumentHidden = (hidden) => { + Object.defineProperty(document, 'hidden', { + configurable: true, + get: () => hidden, + }); + document.dispatchEvent(new Event('visibilitychange')); +}; + +describe('focusTimeout', () => { + let clock, callback; + + beforeEach(() => { + reset() + clock = sinon.useFakeTimers(); + callback = sinon.spy(); + }); + + afterEach(() => { + clock.restore(); + }) + + it('should invoke callback when page is visible', () => { + setFocusTimeout(callback, 2000); + clock.tick(2000); + expect(callback.called).to.be.true; + }); + + it('should not choke if page starts hidden', () => { + setDocumentHidden(true); + reset(); + setDocumentHidden(false); + setFocusTimeout(callback, 1000); + clock.tick(1000); + sinon.assert.called(callback); + }) + + it('should not invoke callback if page was hidden', () => { + setFocusTimeout(callback, 2000); + setDocumentHidden(true); + clock.tick(3000); + expect(callback.called).to.be.false; + }); + + it('should defer callback execution when page is hidden', () => { + setFocusTimeout(callback, 4000); + clock.tick(2000); + setDocumentHidden(true); + clock.tick(2000); + setDocumentHidden(false); + expect(callback.called).to.be.false; + clock.tick(2000); + expect(callback.called).to.be.true; + }); + + it('should not execute deferred callbacks again', () => { + setDocumentHidden(true); + setFocusTimeout(callback, 1000); + clock.tick(2000); + [false, true, false].forEach(setDocumentHidden); + clock.tick(2000); + sinon.assert.calledOnce(callback); + }); + + it('should run callbacks that expire while page is hidden', () => { + setFocusTimeout(callback, 1000); + clock.tick(500); + setDocumentHidden(true); + clock.tick(1000); + setDocumentHidden(false); + sinon.assert.notCalled(callback); + clock.tick(1000); + sinon.assert.called(callback); + }) + + it('should return updated timerId after page was showed again', () => { + const getCurrentTimerId = setFocusTimeout(callback, 4000); + const oldTimerId = getCurrentTimerId(); + clock.tick(2000); + setDocumentHidden(true); + clock.tick(2000); + setDocumentHidden(false); + const newTimerId = getCurrentTimerId(); + expect(oldTimerId).to.not.equal(newTimerId); + }); +}); diff --git a/test/spec/unit/utils/ipUtils_spec.js b/test/spec/unit/utils/ipUtils_spec.js new file mode 100644 index 00000000000..8cd82a8c4fe --- /dev/null +++ b/test/spec/unit/utils/ipUtils_spec.js @@ -0,0 +1,58 @@ +import { scrubIPv4, scrubIPv6 } from '../../../../src/utils/ipUtils' + +describe('ipUtils', () => { + describe('ipv4', () => { + it('should mask ip v4', () => { + let input = '192.168.1.1'; + let output = scrubIPv4(input); + expect(output).to.deep.equal('192.168.1.0'); + input = '192.168.255.255'; + output = scrubIPv4(input); + expect(output).to.deep.equal('192.168.255.0'); + }); + + it('should return null for null input', () => { + let input = null; + let output = scrubIPv4(input); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '192.130.2'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + }); + + describe('ipv6', () => { + it('should mask ip v6', () => { + let input = '2001:db8:3333:4444:CCCC:DDDD:EEEE:FFFF'; + let output = scrubIPv6(input); + expect(output).to.deep.equal('2001:db8:3333:4444:0:0:0:0'); + }); + + it('should return null for null input', () => { + let input = null; + let output = scrubIPv6(input); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = '2001:db8:3333:4444:CCCC:DDDD:EEEE'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + + it('should convert invalid format to null', () => { + let invalidIp = 'invalid'; + let output = scrubIPv4(invalidIp); + expect(output).to.deep.equal(null); + }); + }); +}) diff --git a/test/spec/utils_spec.js b/test/spec/utils_spec.js index 8ea68aadecc..1b4cc3764f6 100644 --- a/test/spec/utils_spec.js +++ b/test/spec/utils_spec.js @@ -1,9 +1,8 @@ import {getAdServerTargeting} from 'test/fixtures/fixtures.js'; import {expect} from 'chai'; -import { TARGETING_KEYS } from 'src/constants.js'; +import {TARGETING_KEYS} from 'src/constants.js'; import * as utils from 'src/utils.js'; -import {getHighestCpm, getLatestHighestCpmBid, getOldestHighestCpmBid} from '../../src/utils/reducers.js'; -import {binarySearch, deepEqual, encodeMacroURI, memoize, waitForElementToLoad} from 'src/utils.js'; +import {binarySearch, deepEqual, encodeMacroURI, memoize, sizesToSizeTuples, waitForElementToLoad} from 'src/utils.js'; import {convertCamelToUnderscore} from '../../libraries/appnexusUtils/anUtils.js'; var assert = require('assert'); @@ -21,6 +20,54 @@ describe('Utils', function () { type_array = 'Array', type_function = 'Function'; + describe('canAccessWindowTop', function () { + let sandbox; + + beforeEach(function () { + sandbox = sinon.sandbox.create(); + }); + + afterEach(function () { + sandbox.restore(); + }); + it('should return true if window.top is accessible', function () { + assert.equal(utils.canAccessWindowTop(), true); + }); + + it('should return false if window.top is not accessible', function () { + sandbox.stub(utils.internal, 'getWindowTop').returns(false); + assert.equal(utils.canAccessWindowTop(), false); + }); + }); + + describe('isSafeFrameWindow', function () { + // SafeFrames implementation + // https://iabtechlab.com/wp-content/uploads/2016/03/SafeFrames_v1.1_final.pdf + const $sf = { + ext: { + geom: function() {} + } + }; + + afterEach(function() { + delete window.$sf; + }) + + it('should return true if window.$sf is accessible', function () { + window.$sf = $sf; + assert.equal(utils.isSafeFrameWindow(), true); + }); + + it('should return false if window.$sf is missimplemented', function () { + window.$sf = {}; + assert.equal(utils.isSafeFrameWindow(), false); + }); + + it('should return false if window.$sf is missing', function () { + assert.equal(utils.isSafeFrameWindow(), false); + }); + }); + describe('getBidIdParameter', function () { it('should return value of the key in input object', function () { var obj = { @@ -65,7 +112,7 @@ describe('Utils', function () { var obj = getAdServerTargeting(); var output = utils.transformAdServerTargetingObj(obj[Object.keys(obj)[0]]); - var expected = 'foobar=0x0%2C300x250%2C300x600&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; + var expected = 'foobar=300x250%2C300x600%2C0x0&' + TARGETING_KEYS.SIZE + '=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '=10.00&' + TARGETING_KEYS.AD_ID + '=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '=appnexus&' + TARGETING_KEYS.SIZE + '_triplelift=0x0&' + TARGETING_KEYS.PRICE_BUCKET + '_triplelift=10.00&' + TARGETING_KEYS.AD_ID + '_triplelift=222bb26f9e8bd&' + TARGETING_KEYS.BIDDER + '_triplelift=triplelift&' + TARGETING_KEYS.SIZE + '_appnexus=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_appnexus=10.00&' + TARGETING_KEYS.AD_ID + '_appnexus=233bcbee889d46d&' + TARGETING_KEYS.BIDDER + '_appnexus=appnexus&' + TARGETING_KEYS.SIZE + '_pagescience=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pagescience=10.00&' + TARGETING_KEYS.AD_ID + '_pagescience=25bedd4813632d7&' + TARGETING_KEYS.BIDDER + '_pagescienc=pagescience&' + TARGETING_KEYS.SIZE + '_brightcom=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brightcom=10.00&' + TARGETING_KEYS.AD_ID + '_brightcom=26e0795ab963896&' + TARGETING_KEYS.BIDDER + '_brightcom=brightcom&' + TARGETING_KEYS.SIZE + '_brealtime=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_brealtime=10.00&' + TARGETING_KEYS.AD_ID + '_brealtime=275bd666f5a5a5d&' + TARGETING_KEYS.BIDDER + '_brealtime=brealtime&' + TARGETING_KEYS.SIZE + '_pubmatic=300x250&' + TARGETING_KEYS.PRICE_BUCKET + '_pubmatic=10.00&' + TARGETING_KEYS.AD_ID + '_pubmatic=28f4039c636b6a7&' + TARGETING_KEYS.BIDDER + '_pubmatic=pubmatic&' + TARGETING_KEYS.SIZE + '_rubicon=300x600&' + TARGETING_KEYS.PRICE_BUCKET + '_rubicon=10.00&' + TARGETING_KEYS.AD_ID + '_rubicon=29019e2ab586a5a&' + TARGETING_KEYS.BIDDER + '_rubicon=rubicon'; assert.equal(output, expected); }); @@ -119,6 +166,43 @@ describe('Utils', function () { }); }); + describe('sizesToSizeTuples', () => { + Object.entries({ + 'single size, numerical': { + in: [1, 2], + out: [[1, 2]] + }, + 'single size, numerical, nested': { + in: [[1, 2]], + out: [[1, 2]] + }, + 'multiple sizes, numerical': { + in: [[1, 2], [3, 4]], + out: [[1, 2], [3, 4]] + }, + 'single size, string': { + in: '1x2', + out: [[1, 2]] + }, + 'multiple sizes, string': { + in: '1x2, 4x3', + out: [[1, 2], [4, 3]] + }, + 'incorrect size, numerical': { + in: [1], + out: [] + }, + 'incorrect size, string': { + in: '1x', + out: [] + } + }).forEach(([t, {in: input, out}]) => { + it(`can parse ${t}`, () => { + expect(sizesToSizeTuples(input)).to.eql(out); + }) + }) + }) + describe('parseSizesInput', function () { it('should return query string using multi size array', function () { var sizes = [[728, 90], [970, 90]]; @@ -1086,6 +1170,44 @@ describe('Utils', function () { }); }); + describe('getUnixTimestampFromNow', () => { + it('correctly obtains unix timestamp', () => { + const nowValue = new Date('2024-01-01').valueOf(); + sinon.stub(Date, 'now').returns(nowValue); + let val = utils.getUnixTimestampFromNow(); + expect(val).equal(nowValue); + + val = utils.getUnixTimestampFromNow(1); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24)); + + val = utils.getUnixTimestampFromNow(1, 'd'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24)); + + val = utils.getUnixTimestampFromNow(1, 'm'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24 / 1440)); + + val = utils.getUnixTimestampFromNow(2, 'm'); + expect(val).equal(nowValue + (1000 * 60 * 60 * 24 * 2 / 1440)); + + // any value that isn't 'm' or 'd' gets treated as Date.now(); + val = utils.getUnixTimestampFromNow(10, 'o'); + expect(val).equal(nowValue); + }); + }); + + describe('convertObjectToArray', () => { + it('correctly converts object to array', () => { + const obj = {key: 1, anotherKey: 'fred', third: ['fred'], fourth: {sub: {obj: 'test'}}}; + const array = utils.convertObjectToArray(obj); + + expect(JSON.stringify(array[0])).equal(JSON.stringify({'key': 1})) + expect(JSON.stringify(array[1])).equal(JSON.stringify({'anotherKey': 'fred'})) + expect(JSON.stringify(array[2])).equal(JSON.stringify({'third': ['fred']})) + expect(JSON.stringify(array[3])).equal(JSON.stringify({'fourth': {sub: {obj: 'test'}}})); + expect(array.length).to.equal(4); + }); + }); + describe('setScriptAttributes', () => { it('correctly adds attributes from an object', () => { const script = document.createElement('script'), @@ -1101,6 +1223,25 @@ describe('Utils', function () { expect(script.id).to.equal('newId'); }); }); + + describe('safeJSONParse', () => { + it('correctly encodes valid input', () => { + const jsonObj = { + key1: 'val1', + key2: { + key3: 100, + key4: true + } + }; + const result = utils.safeJSONEncode(jsonObj); + expect(result).to.equal(`{"key1":"val1","key2":{"key3":100,"key4":true}}`); + }); + it('return empty string for stringify errors', () => { + const jsonObj = {k: 2n}; + const result = utils.safeJSONEncode(jsonObj); + expect(result).to.equal(''); + }); + }); }); describe('memoize', () => { diff --git a/test/spec/videoCache_spec.js b/test/spec/videoCache_spec.js index fc6e71779cb..7d07da9de90 100644 --- a/test/spec/videoCache_spec.js +++ b/test/spec/videoCache_spec.js @@ -1,39 +1,13 @@ import chai from 'chai'; -import {getCacheUrl, store} from 'src/videoCache.js'; +import {batchingCache, getCacheUrl, store, _internal, storeBatch} from 'src/videoCache.js'; import {config} from 'src/config.js'; import {server} from 'test/mocks/xhr.js'; import {auctionManager} from '../../src/auctionManager.js'; import {AuctionIndex} from '../../src/auctionIndex.js'; -import {batchingCache} from '../../src/auction.js'; +import * as utils from 'src/utils.js'; const should = chai.should(); -function getMockBid(bidder, auctionId, bidderRequestId) { - return { - 'bidder': bidder, - 'params': { - 'placementId': '10433394', - 'member': 123, - 'keywords': { - 'foo': ['bar', 'baz'], - 'fizz': ['buzz'] - } - }, - 'bid_id': '12345abc', - 'adUnitCode': 'div-gpt-ad-1460505748561-0', - 'mediaTypes': { - 'banner': { - 'sizes': [[300, 250]] - } - }, - 'transactionId': '4ef956ad-fd83-406d-bd35-e4bb786ab86c', - 'sizes': [300, 250], - 'bidId': '123', - 'bidderRequestId': bidderRequestId, - 'auctionId': auctionId - }; -} - describe('The video cache', function () { function assertError(callbackSpy) { callbackSpy.calledOnce.should.equal(true); @@ -126,9 +100,7 @@ describe('The video cache', function () { prebid.org wrapper - - - + \n \n
`; @@ -335,34 +307,36 @@ describe('The video cache', function () { JSON.parse(request.requestBody).should.deep.equal(payload); }); - it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { - const mockAfterBidAdded = function() {}; - let callback = null; - let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); + if (FEATURES.VIDEO) { + it('should wait the duration of the batchTimeout and pass the correct batchSize if batched requests are enabled in the config', () => { + const mockAfterBidAdded = function() {}; + let callback = null; + let mockTimeout = sinon.stub().callsFake((cb) => { callback = cb }); - config.setConfig({ - cache: { - url: 'https://prebid.adnxs.com/pbc/v1/cache', - batchSize: 3, - batchTimeout: 20 - } - }); + config.setConfig({ + cache: { + url: 'https://prebid.adnxs.com/pbc/v1/cache', + batchSize: 3, + batchTimeout: 20 + } + }); - let stubCache = sinon.stub(); - const batchAndStore = batchingCache(mockTimeout, stubCache); - for (let i = 0; i < 3; i++) { - batchAndStore({}, {}, mockAfterBidAdded); - } + let stubCache = sinon.stub(); + const batchAndStore = batchingCache(mockTimeout, stubCache); + for (let i = 0; i < 3; i++) { + batchAndStore({}, {}, mockAfterBidAdded); + } - sinon.assert.calledOnce(mockTimeout); - sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); + sinon.assert.calledOnce(mockTimeout); + sinon.assert.calledWith(mockTimeout, sinon.match.any, 20); - const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; + const expectedBatch = [{ afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }, { afterBidAdded: mockAfterBidAdded, auctionInstance: { }, bidResponse: { } }]; - callback(); + callback(); - sinon.assert.calledWith(stubCache, expectedBatch); - }); + sinon.assert.calledWith(stubCache, expectedBatch); + }); + } function assertRequestMade(bid, expectedValue) { store([bid], function () { }); @@ -393,6 +367,35 @@ describe('The video cache', function () { return callback; } }); + + describe('storeBatch', () => { + let sandbox; + let err, cacheIds + beforeEach(() => { + err = null; + cacheIds = []; + sandbox = sinon.createSandbox(); + sandbox.stub(utils, 'logError'); + sandbox.stub(_internal, 'store').callsFake((_, cb) => cb(err, cacheIds)); + }); + afterEach(() => { + sandbox.restore(); + }) + it('should log an error when store replies with an error', () => { + err = new Error('err'); + storeBatch([]); + sinon.assert.called(utils.logError); + }); + it('should not process returned uuids if they do not match the batch size', () => { + const el = {auctionInstance: {}, bidResponse: {}, afterBidAdded: sinon.stub()} + const batch = [el, el]; + cacheIds = [{uuid: 'mock-id'}] + storeBatch(batch); + expect(el.bidResponse.videoCacheKey).to.not.exist; + sinon.assert.notCalled(batch[0].afterBidAdded); + sinon.assert.called(utils.logError); + }) + }) }); describe('The getCache function', function () { diff --git a/test/spec/video_spec.js b/test/spec/video_spec.js index 3252c58c687..18955771049 100644 --- a/test/spec/video_spec.js +++ b/test/spec/video_spec.js @@ -1,12 +1,26 @@ -import {fillVideoDefaults, isValidVideoBid} from 'src/video.js'; +import {fillVideoDefaults, isValidVideoBid, validateOrtbVideoFields} from 'src/video.js'; import {hook} from '../../src/hook.js'; import {stubAuctionIndex} from '../helpers/indexStub.js'; +import * as utils from '../../src/utils.js'; describe('video.js', function () { + let sandbox; + let utilsMock; + before(() => { hook.ready(); }); + beforeEach(() => { + sandbox = sinon.createSandbox(); + utilsMock = sandbox.mock(utils); + }) + + afterEach(() => { + utilsMock.restore(); + sandbox.restore(); + }); + describe('fillVideoDefaults', () => { function fillDefaults(videoMediaType = {}) { const adUnit = {mediaTypes: {video: videoMediaType}}; @@ -77,6 +91,94 @@ describe('video.js', function () { }) }) + describe('validateOrtbVideoFields', () => { + it('remove incorrect ortb properties, and keep non ortb ones', () => { + sandbox.spy(utils, 'logWarn'); + + const mt = { + content: 'outstream', + + mimes: ['video/mp4'], + minduration: 5, + maxduration: 15, + startdelay: 0, + maxseq: 0, + poddur: 0, + protocols: [7], + w: 600, + h: 480, + podid: 'an-id', + podseq: 0, + rqddurs: [5], + placement: 1, + plcmt: 1, + linearity: 1, + skip: 0, + skipmin: 3, + skipafter: 3, + sequence: 0, + slotinpod: 0, + mincpmpersec: 2.5, + battr: [6, 7], + maxextended: 0, + minbitrate: 800, + maxbitrate: 1000, + boxingallowed: 1, + playbackmethod: [1], + playbackend: 1, + delivery: [2], + pos: 0, + api: 6, // -- INVALID + companiontype: [1, 2, 3], + poddedupe: [1], + + otherOne: 'test', + }; + + const expected = {...mt}; + delete expected.api; + + const adUnit = { + code: 'adUnitCode', + mediaTypes: { video: mt } + }; + validateOrtbVideoFields(adUnit); + + expect(adUnit.mediaTypes.video).to.eql(expected); + sinon.assert.callCount(utils.logWarn, 1); + }); + + it('Early return when 1st param is not a plain object', () => { + sandbox.spy(utils, 'logWarn'); + + validateOrtbVideoFields(); + validateOrtbVideoFields([]); + validateOrtbVideoFields(null); + validateOrtbVideoFields('hello'); + validateOrtbVideoFields(() => {}); + + sinon.assert.callCount(utils.logWarn, 5); + }); + + it('Calls onInvalidParam when a property is invalid', () => { + const onInvalidParam = sandbox.spy(); + const adUnit = { + code: 'adUnitCode', + mediaTypes: { + video: { + content: 'outstream', + mimes: ['video/mp4'], + api: 6 + } + } + }; + validateOrtbVideoFields(adUnit, onInvalidParam); + + sinon.assert.calledOnce(onInvalidParam); + sinon.assert.calledWith(onInvalidParam, 'api', 6, adUnit); + }); + }) + describe('isValidVideoBid', () => { it('validates valid instream bids', function () { const bid = { diff --git a/test/test_deps.js b/test/test_deps.js index c8a3bcc9426..3f0f766b457 100644 --- a/test/test_deps.js +++ b/test/test_deps.js @@ -19,3 +19,4 @@ require('test/helpers/prebidGlobal.js'); require('test/mocks/adloaderStub.js'); require('test/mocks/xhr.js'); require('test/mocks/analyticsStub.js'); +require('test/mocks/ortbConverter.js') diff --git a/test/test_index.js b/test/test_index.js index ce9b671be89..030082735c3 100644 --- a/test/test_index.js +++ b/test/test_index.js @@ -1,25 +1,4 @@ -[it, describe].forEach((ob) => { - ob.only = function () { - [ - 'describe.only and it.only are disabled unless you provide a single spec --file,', - 'because they can silently break the pipeline tests', - // eslint-disable-next-line no-console - ].forEach(l => console.error(l)) - throw new Error('do not use .only()') - }; -}); - -[it, describe].forEach((ob) => { - ob.skip = function () { - [ - 'describe.skip and it.skip are disabled,', - 'because they pollute the pipeline test output', - // eslint-disable-next-line no-console - ].forEach(l => console.error(l)) - throw new Error('do not use .skip()') - }; -}); - +require('./pipeline_setup.js'); require('./test_deps.js'); var testsContext = require.context('.', true, /_spec$/); diff --git a/wdio.shared.conf.js b/wdio.shared.conf.js index 34e1ee9c675..08b46eaab91 100644 --- a/wdio.shared.conf.js +++ b/wdio.shared.conf.js @@ -16,7 +16,7 @@ exports.config = { mochaOpts: { ui: 'bdd', timeout: 60000, - compilers: ['js:babel-register'], + compilers: ['js:@babel/register'], }, // if you see error, update this to spec reporter and logLevel above to get detailed report. reporters: ['spec'] diff --git a/webpack.conf.js b/webpack.conf.js index 1035e985b22..5b0d864045e 100644 --- a/webpack.conf.js +++ b/webpack.conf.js @@ -126,7 +126,6 @@ module.exports = { }) ); const core = path.resolve('./src'); - const paapiMod = path.resolve('./modules/paapi.js'); return Object.assign(libraries, { core: { @@ -135,16 +134,6 @@ module.exports = { return module.resource && module.resource.startsWith(core); } }, - paapi: { - // fledgeForGpt imports paapi to keep backwards compat for NPM consumers - // this makes the paapi module its own chunk, pulled in by both paapi and fledgeForGpt entry points, - // to avoid duplication - // TODO: remove this in prebid 9 - name: 'chunk-paapi', - test: (module) => { - return module.resource === paapiMod; - } - } }, { default: false, defaultVendors: false