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..609baf82c08 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. @@ -374,7 +374,7 @@ 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 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/babelConfig.js b/babelConfig.js index aa4670969ca..80e0222038c 100644 --- a/babelConfig.js +++ b/babelConfig.js @@ -26,7 +26,7 @@ module.exports = function (options = {}) { const plugins = [ [path.resolve(__dirname, './plugins/pbjsGlobals.js'), options], [useLocal('@babel/plugin-transform-runtime')], - [useLocal('@babel/plugin-proposal-private-methods')], + [useLocal('@babel/plugin-proposal-private-methods')], ]; if (options.codeCoverage) { plugins.push([useLocal('babel-plugin-istanbul')]) 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/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..5f66dd9c8e2 --- /dev/null +++ b/integrationExamples/gpt/51DegreesRtdProvider_example.html @@ -0,0 +1,196 @@ + + + + + + + + + + + + 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/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/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/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..0b37f80f27c --- /dev/null +++ b/integrationExamples/top-level-paapi/no_adserver.html @@ -0,0 +1,113 @@ + + + + + + + + + +

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/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..08c8e43cea3 --- /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/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/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..0bd52172a41 --- /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/detectBrowserUtils/detectBrowserUtils.js b/libraries/detectBrowserUtils/detectBrowserUtils.js new file mode 100644 index 00000000000..0606388a346 --- /dev/null +++ b/libraries/detectBrowserUtils/detectBrowserUtils.js @@ -0,0 +1,72 @@ +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 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/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..f19e2bfc29a --- /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/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..25c1de03538 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 @@ -35,3 +36,24 @@ 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) +} 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/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/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/bidUtils.js b/libraries/precisoUtils/bidUtils.js new file mode 100644 index 00000000000..95278cd1013 --- /dev/null +++ b/libraries/precisoUtils/bidUtils.js @@ -0,0 +1,103 @@ +import { convertOrtbRequestToProprietaryNative } from '../../src/native.js'; +import { replaceAuctionPrice } from '../../src/utils.js'; +import { ajax } from '../../src/ajax.js'; +import { consentCheck } from './bidUtilsCommon.js'; + +export const buildRequests = (endpoint) => (validBidRequests = [], bidderRequest) => { + // convert Native ORTB definition to old-style prebid native definition + validBidRequests = convertOrtbRequestToProprietaryNative(validBidRequests); + var city = Intl.DateTimeFormat().resolvedOptions().timeZone; + let req = { + // bidRequest: bidderRequest, + id: validBidRequests[0].auctionId, + cur: validBidRequests[0].params.currency || ['USD'], + imp: validBidRequests.map(req => { + const { bidId, sizes } = req + const impValue = { + id: bidId, + bidfloor: req.params.bidFloor, + bidfloorcur: req.params.currency + } + if (req.mediaTypes.banner) { + impValue.banner = { + format: (req.mediaTypes.banner.sizes || sizes).map(size => { + return { w: size[0], h: size[1] } + }), + + } + } + return impValue + }), + 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 + }; + + // req.language.indexOf('-') != -1 && (req.language = req.language.split('-')[0]) + 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); + } +} + +/* replacing auction_price macro from adm */ +function macroReplace(adm, cpm) { + let replacedadm = replaceAuctionPrice(adm, cpm); + return replacedadm; +} diff --git a/libraries/precisoUtils/bidUtilsCommon.js b/libraries/precisoUtils/bidUtilsCommon.js new file mode 100644 index 00000000000..cb9699035fb --- /dev/null +++ b/libraries/precisoUtils/bidUtilsCommon.js @@ -0,0 +1,173 @@ +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); + + // 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: 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'); + + // Base sync URL + 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..25c7183d552 --- /dev/null +++ b/libraries/riseUtils/index.js @@ -0,0 +1,331 @@ +import { + isArray, + isFn, + deepAccess, + isEmpty, + contains, + isInteger, + getBidIdParameter +} 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 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..f18540818cb --- /dev/null +++ b/libraries/targetVideoUtils/bidderUtils.js @@ -0,0 +1,178 @@ +import {VIDEO} from '../../src/mediaTypes.js'; +import {getRefererInfo} from '../../src/refererDetection.js'; +import {createTrackPixelHtml, deepAccess, getBidRequest} 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 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..8ce94c0eaeb --- /dev/null +++ b/libraries/targetVideoUtils/constants.js @@ -0,0 +1,23 @@ +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 VIDEO_PARAMS = [ + 'api', 'linearity', 'maxduration', 'mimes', 'minduration', + 'plcmt', 'playbackmethod', 'protocols', 'startdelay' +]; + +export { + SOURCE, + GVLID, + MARGIN, + BIDDER_CODE, + 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..96195eed70d --- /dev/null +++ b/libraries/teqblazeUtils/bidderUtils.js @@ -0,0 +1,257 @@ +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; + } + + 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/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/vidazooUtils/bidderUtils.js b/libraries/vidazooUtils/bidderUtils.js new file mode 100644 index 00000000000..fbb4300cb24 --- /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 = 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/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..abbd14db6a9 --- /dev/null +++ b/libraries/xeUtils/bidderUtils.js @@ -0,0 +1,158 @@ +import {deepAccess, getBidIdParameter, isFn, logError, isArray, parseSizesInput} 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 (typeof floor === 'object' && !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/modules.json b/modules.json index 27840c64ecd..f7d8d7a053b 100644 --- a/modules.json +++ b/modules.json @@ -10,12 +10,11 @@ "concertBidAdapter", "connectIdSystem", "consentManagementUsp", - "consentManagement", + "consentManagementTcf", "conversantBidAdapter", "criteoBidAdapter", "criteoIdSystem", "dfpAdServerVideo", - "enrichmentFpdModule", "fabrickIdSystem", "gptPreAuction", "gridBidAdapter", @@ -58,7 +57,7 @@ "userId", "vidazooBidAdapter", "videojsVideoProvider", - "yahoosspBidAdapter", + "yahooAdsBidAdapter", "yieldmoBidAdapter", "pulsepointBidAdapter", "pulsepointAnalyticsAdapter" diff --git a/modules/.submodules.json b/modules/.submodules.json index 9dfeaf910f8..97017237909 100644 --- a/modules/.submodules.json +++ b/modules/.submodules.json @@ -6,7 +6,6 @@ "adtelligentIdSystem", "adqueryIdSystem", "amxIdSystem", - "britepoolIdSystem", "connectIdSystem", "czechAdIdSystem", "criteoIdSystem", @@ -37,7 +36,6 @@ "novatiqIdSystem", "oneKeyIdSystem", "operaadsIdSystem", - "parrableIdSystem", "pubProvidedIdSystem", "publinkIdSystem", "quantcastIdSystem", @@ -45,22 +43,24 @@ "tapadIdSystem", "teadsIdSystem", "tncIdSystem", - "utiqSystem", + "utiqIdSystem", "utiqMtpIdSystem", "uid2IdSystem", "euidIdSystem", "unifiedIdSystem", "verizonMediaIdSystem", - "zeotapIdPlusIdSystem" + "zeotapIdPlusIdSystem", + "yandexIdSystem" ], "adpod": [ "freeWheelAdserverVideo", - "dfpAdServerVideo" + "dfpAdpod" ], "rtdModule": [ "1plusXRtdProvider", "a1MediaRtdProvider", "aaxBlockmeterRtdProvider", + "adagioRtdProvider", "adlooxRtdProvider", "adnuntiusRtdProvider", "airgridRtdProvider", @@ -80,6 +80,7 @@ "greenbidsRtdProvider", "growthCodeRtdProvider", "hadronRtdProvider", + "humansecurityRtdProvider", "iasRtdProvider", "idWardRtdProvider", "imRtdProvider", @@ -87,6 +88,7 @@ "jwplayerRtdProvider", "medianetRtdProvider", "mgidRtdProvider", + "mobianRtdProvider", "neuwoRtdProvider", "oneKeyRtdProvider", "optimeraRtdProvider", @@ -97,8 +99,10 @@ "reconciliationRtdProvider", "relevadRtdProvider", "sirdataRtdProvider", + "symitriDapRtdProvider", "timeoutRtdProvider", - "weboramaRtdProvider" + "weboramaRtdProvider", + "wurflRtdProvider" ], "fpdModule": [ "validationFpdModule", @@ -109,7 +113,8 @@ "videojsVideoProvider" ], "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..ae002d79d58 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 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..ec2e5235445 --- /dev/null +++ b/modules/51DegreesRtdProvider.js @@ -0,0 +1,244 @@ +import {loadExternalScript} from '../src/adloader.js'; +import {submodule} from '../src/hook.js'; +import {prefixLog, deepAccess, mergeDeep} 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 + const resourceKey = deepAccess(moduleConfig, 'params.resourceKey'); + // On-premise JS URL + const onPremiseJSUrl = deepAccess(moduleConfig, 'params.onPremiseJSUrl'); + + 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'); + } + if (resourceKey === '') { + throw new Error(LOG_PREFIX + ' replace in configuration with a resource key obtained from https://configure.51degrees.com/tWrhNfY6'); + } + + 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 + * @returns {string} 51Degrees JS URL + */ +export const get51DegreesJSURL = (pathData) => { + if (pathData.onPremiseJSUrl) { + return pathData.onPremiseJSUrl; + } + return `https://cloud.51degrees.com/api/v4/${pathData.resourceKey}.js`; +} + +/** + * 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 setOrtb2KeyIfNotEmpty = (obj, key, value) => { + if (!key) { + throw new Error(LOG_PREFIX + ' Key is required'); + } + + if (value) { + obj[key] = value; + } +} + +/** + * Converts 51Degrees device data to ORTB2 format + * + * @param {Object} device + * @param {string} [device.deviceid] Device ID (unique 51Degrees identifier) + * @param {string} [device.devicetype] + * @param {string} [device.hardwarevendor] + * @param {string} [device.hardwaremodel] + * @param {string[]} [device.hardwarename] + * @param {string} [device.platformname] + * @param {string} [device.platformversion] + * @param {number} [device.screenpixelsheight] + * @param {number} [device.screenpixelswidth] + * @param {number} [device.pixelratio] + * @param {number} [device.screeninchesheight] + * + * @returns {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 devicePPI = device.screenpixelsheight && device.screeninchesheight + ? Math.round(device.screenpixelsheight / device.screeninchesheight) + : null; + + setOrtb2KeyIfNotEmpty(ortb2Device, 'devicetype', ORTB_DEVICE_TYPE_MAP.get(device.devicetype)); + setOrtb2KeyIfNotEmpty(ortb2Device, 'make', device.hardwarevendor); + setOrtb2KeyIfNotEmpty(ortb2Device, 'model', deviceModel); + setOrtb2KeyIfNotEmpty(ortb2Device, 'os', device.platformname); + setOrtb2KeyIfNotEmpty(ortb2Device, 'osv', device.platformversion); + setOrtb2KeyIfNotEmpty(ortb2Device, 'h', device.screenpixelsheight); + setOrtb2KeyIfNotEmpty(ortb2Device, 'w', device.screenpixelswidth); + setOrtb2KeyIfNotEmpty(ortb2Device, 'pxratio', device.pixelratio); + setOrtb2KeyIfNotEmpty(ortb2Device, 'ppi', devicePPI); + + if (device.deviceid) { + ortb2Device.ext = { + 'fiftyonedegrees_deviceId': device.deviceid + }; + } + + return 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); + + // Get 51Degrees JS URL, which is either cloud or on-premise + const scriptURL = get51DegreesJSURL(resourceKey ? {resourceKey} : {onPremiseJSUrl}); + logMessage('URL of the script to be injected: ', scriptURL); + + // 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'); + } + } + + // Inject 51Degrees script, get device data and merge it into the ORTB2 object + loadExternalScript(scriptURL, 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, + {device: convert51DegreesDeviceToOrtb2(data.device)}, + ); + logMessage('reqBidsConfigObj: ', reqBidsConfigObj); + callback(); + }); + }); + } 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..18f346d37a8 --- /dev/null +++ b/modules/51DegreesRtdProvider.md @@ -0,0 +1,150 @@ +# 51Degrees RTD Submodule + +## Overview + + Module Name: 51Degrees Rtd Provider + Module Type: Rtd Provider + Maintainer: support@51degrees.com + +## Description + +51Degrees module enriches an OpenRTB request with [51Degrees Device Data](https://51degrees.com/documentation/index.html). + +51Degrees module sets the following fields of the device object: `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 the device age, chip set, codec support, and 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/tWrhNfY6). 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 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 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 GetHighEntropyValues API instead? + +Thanks for asking. + +The script this module injects has a fall back to the GetHighEntropyValues API, but does not rely on it as a first (or only) choice route - please see the illustrative cases below. Albeit it seems easier, GHEV API is not supported by all browsers (so the decision to call it should be conditional) and also even in Chrome this API will likely be a 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 future proof method. + +##### Illustrative Cases + +* if the device is iPhone/iPad then there is no point 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 then there is similarly no point requesting GHEV as the API is not supported. + +* if the browser is Chrome then the `Delegate-CH` if enabled by the publisher would enable 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 make sure `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 end point + // 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 and `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) | | + +## 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. \ No newline at end of file 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/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..2f015f07c31 100644 --- a/modules/adagioAnalyticsAdapter.js +++ b/modules/adagioAnalyticsAdapter.js @@ -2,12 +2,13 @@ * 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'; const emptyUrl = ''; @@ -19,6 +20,13 @@ 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; + } +}; + const cache = { auctions: {}, getAuction: function(auctionId, adUnitCode) { @@ -48,34 +56,6 @@ const cache = { }; const enc = window.encodeURIComponent; -/** -/* BEGIN ADAGIO.JS CODE - */ - -function canAccessTopWindow() { - try { - if (getWindowTop().location.href) { - return true; - } - } catch (error) { - return false; - } -}; - -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 - */ - /** * UTILS FUNCTIONS */ @@ -150,6 +130,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 +178,28 @@ function getTargetedAuctionId(bid) { */ function handlerAuctionInit(event) { - const w = getCurrentWindow(); + const w = getBestWindowForAdagio(); const prebidAuctionId = event.auctionId; const adUnitCodes = removeDuplicates(event.adUnitCodes, adUnitCode => adUnitCode); // 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 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); @@ -239,45 +214,60 @@ function handlerAuctionInit(event) { 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 biddersSrc = sortedBidderNames.map(bidSrcMapper).join(','); + const biddersCode = sortedBidderNames.map(bidder => adapterManager.resolveAlias(bidder)).join(','); - const adagioAuctionId = params.adagioAuctionId; - cache.addPrebidAuctionIdRef(prebidAuctionId, adagioAuctionId); + // if adagio was involved in the auction we identified it with rtdUid, if not use the prebid auctionId + const auctionId = rtdUid || prebidAuctionId; - // 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(); + 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; sendNewBeacon(prebidAuctionId, adUnitCode); }); @@ -324,13 +314,21 @@ function handlerAuctionEnd(event) { return bid ? getCurrencyData(bid).netCpm : null } + const perfNavigation = performance.getEntriesByType('navigation')[0]; + 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, }); + sendNewBeacon(auctionId, adUnitCode); }); } + function handlerBidWon(event) { let auctionId = getTargetedAuctionId(event); @@ -345,6 +343,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 +353,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,6 +378,33 @@ function handlerAdRender(event, isSuccess) { sendNewBeacon(auctionId, adUnitCode); }; +/** + * 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 */ @@ -400,6 +432,9 @@ 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; } } catch (error) { logError('Error on Adagio Analytics Adapter', error); @@ -407,7 +442,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,14 +457,26 @@ 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); } 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..cb125d4446e 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) { @@ -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..91f446e68fd --- /dev/null +++ b/modules/adagioRtdProvider.js @@ -0,0 +1,696 @@ +/** + * 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 } = _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, + 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, 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. + */ + +/** + * @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..925c0b3500e 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'; @@ -78,6 +78,7 @@ export const spec = { 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..17156280c0d --- /dev/null +++ b/modules/adgridBidAdapter.js @@ -0,0 +1,175 @@ +import { _each, isEmpty, deepAccess } 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 = Object.freeze({ + CODE: 'adgrid', + HOST: 'https://api-prebid.adgrid.io', + REQUEST_METHOD: 'POST', + REQUEST_ENDPOINT: '/api/v1/auction', + SUPPORTED_MEDIA_TYPES: [BANNER], +}); + +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, + 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; + } + } + + 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..36a359170c4 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 @@ -94,7 +95,10 @@ export const spec = { {code: 'adpluto'}, {code: 'headbidder'}, {code: 'digiad'}, - {code: 'monetix'} + {code: 'monetix'}, + {code: 'hyperbrainz'}, + {code: 'voisetech'}, + {code: 'global_sun'} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], @@ -245,18 +249,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} diff --git a/modules/adlooxAnalyticsAdapter.js b/modules/adlooxAnalyticsAdapter.js index e31ea290e11..f18c9e72337 100644 --- a/modules/adlooxAnalyticsAdapter.js +++ b/modules/adlooxAnalyticsAdapter.js @@ -232,6 +232,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); } 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..a5ebe91f98a 100644 --- a/modules/admaticBidAdapter.js +++ b/modules/admaticBidAdapter.js @@ -3,6 +3,7 @@ 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'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -11,7 +12,7 @@ import { Renderer } from '../src/Renderer.js'; */ export const OPENRTB = { - NATIVE: { + N: { IMAGE_TYPE: { ICON: 1, MAIN: 3, @@ -40,7 +41,8 @@ export const spec = { code: BIDDER_CODE, gvlid: 1281, aliases: [ - {code: 'pixad', gvlid: 1281} + {code: 'pixad', gvlid: 1281}, + {code: 'monetixads', gvlid: 1281} ], supportedMediaTypes: [BANNER, VIDEO, NATIVE], /** @@ -72,7 +74,6 @@ export const spec = { 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'; const bidderName = validBidRequests[0].bidder; const payload = { @@ -87,7 +88,6 @@ export const spec = { }, imp: bids, ext: { - cur: currency, bidder: bidderName }, schain: {}, @@ -102,6 +102,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,11 +144,14 @@ export const spec = { if (payload) { switch (bidderName) { + case 'monetixads': + SYNC_URL = 'https://static.cdn.monetixads.com/sync.html'; + break; case 'pixad': SYNC_URL = 'https://static.cdn.pixad.com.tr/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; } @@ -155,26 +162,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; @@ -203,7 +191,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: { @@ -428,30 +416,30 @@ function interpretNativeAd(adm) { }; native.assets.forEach(asset => { switch (asset.id) { - case OPENRTB.NATIVE.ASSET_ID.TITLE: + case OPENRTB.N.ASSET_ID.TITLE: result.title = asset.title.text; break; - case OPENRTB.NATIVE.ASSET_ID.IMAGE: + case OPENRTB.N.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: + case OPENRTB.N.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: + case OPENRTB.N.ASSET_ID.BODY: result.body = asset.data.value; break; - case OPENRTB.NATIVE.ASSET_ID.SPONSORED: + case OPENRTB.N.ASSET_ID.SPONSORED: result.sponsoredBy = asset.data.value; break; - case OPENRTB.NATIVE.ASSET_ID.CTA: + case OPENRTB.N.ASSET_ID.CTA: result.cta = asset.data.value; break; } diff --git a/modules/admixerBidAdapter.js b/modules/admixerBidAdapter.js index de7b941d614..4deeaee5206 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,10 +117,16 @@ 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', @@ -128,4 +138,5 @@ function getBidFloor(bid) { 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..d017b6a8398 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 {isStr, isEmpty, deepAccess, getUnixTimestampFromNow, convertObjectToArray} from '../src/utils.js'; import { config } from '../src/config.js'; import { getStorageManager } from '../src/storageManager.js'; @@ -19,10 +19,6 @@ 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); - } }; const storageTool = (function () { @@ -50,11 +46,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 +69,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 +82,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 +126,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 +142,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'; } @@ -227,11 +246,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 = {}; @@ -254,16 +275,23 @@ export const spec = { 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 bidTargeting = {...bid.params.targeting || {}}; + targetingTool.mergeKvsFromOrtb(bidTargeting, bidderRequest); + const adUnit = { ...bidTargeting, 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; 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..fd24ccdeecb 100644 --- a/modules/adotBidAdapter.js +++ b/modules/adotBidAdapter.js @@ -197,7 +197,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, 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/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..955e908f000 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'; 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/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..c6dde0d3f6c 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); } }; 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/akamaiDapRtdProvider.js b/modules/akamaiDapRtdProvider.js index 0bd53b2a91f..e5a647a90ef 100644 --- a/modules/akamaiDapRtdProvider.js +++ b/modules/akamaiDapRtdProvider.js @@ -5,802 +5,23 @@ * @module modules/akamaiDapRtdProvider * @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 { loadExternalScript } from '../src/adloader.js'; -import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; -/** - * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule - */ - -const MODULE_NAME = 'realTimeData'; -const SUBMODULE_NAME = 'dap'; -const MODULE_CODE = 'akamaidap'; - -export const DAP_TOKEN = 'async_dap_token'; -export const DAP_MEMBERSHIP = 'async_dap_membership'; -export const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; -export const DAP_SS_ID = 'dap_ss_id'; -export const DAP_DEFAULT_TOKEN_TTL = 3600; // in seconds -export const DAP_MAX_RETRY_TOKENIZE = 1; -export const DAP_CLIENT_ENTROPY = 'dap_client_entropy' - -export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); -let dapRetryTokenize = 0; - -/** - * Lazy merge objects. - * @param {String} target - * @param {String} source - */ -function mergeLazy(target, source) { - if (!isPlainObject(target)) { - target = {}; - } - if (!isPlainObject(source)) { - source = {}; - } - return mergeDeep(target, source); -} - -/** - * Add real-time data & merge segments. - * @param {Object} ortb2 destination object to merge RTD into - * @param {Object} rtd - */ -export function addRealTimeData(ortb2, rtd) { - logInfo('DEBUG(addRealTimeData) - ENTER'); - if (isPlainObject(rtd.ortb2)) { - logMessage('DEBUG(addRealTimeData): merging original: ', ortb2); - logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2); - mergeLazy(ortb2, rtd.ortb2); - } - logInfo('DEBUG(addRealTimeData) - EXIT'); -} - -/** - * Real-time data retrieval from Audigent - * @param {Object} bidConfig - * @param {function} onDone - * @param {Object} rtdConfig - * @param {Object} userConsent - */ -export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); - 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 (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) }); - } else { - reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); - } - } - } - }); - loadScriptPromise - .catch((error) => { - logError('Entropy could not be calculated due to: ', error.message); - }) - .finally(() => { - generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); - }); -} - -export function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { - logInfo('DEBUG(generateRealTimeData) - ENTER'); - logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); - logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); - dapRetryTokenize = 0; - var jsonData = null; - if (rtdConfig && isPlainObject(rtdConfig.params)) { - if (rtdConfig.params.segtax == 504) { - let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); - if (encMembership) { - jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) - } - } else { - let membership = dapUtils.dapGetMembershipFromLocalStorage(); - if (membership) { - jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) - } - } - } - if (jsonData) { - if (jsonData.rtd) { - addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd); - onDone(); - logInfo('DEBUG(generateRealTimeData) - 1'); - // Don't return - ensure the data is always fresh. - } - } - // Calling setTimeout to release the main thread so that the bid request could be sent. - setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); -} - -/** - * Module init - * @param {Object} provider - * @param {Object} userConsent - * @return {boolean} - */ -function init(provider, userConsent) { - if (dapUtils.checkConsent(userConsent) === false) { - return false; - } - return true; -} - -/** @type {RtdSubmodule} */ -export const akamaiDapRtdSubmodule = { - name: SUBMODULE_NAME, - getBidRequestData: getRealTimeData, - init: init -}; - -submodule(MODULE_NAME, akamaiDapRtdSubmodule); -export const dapUtils = { - - 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} - }; - 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 - refreshMembership = false; - } else { - 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 - } - }, - dapGetEntropy: function(resolve, reject) { - if (typeof window.dapCalculateEntropy === 'function') { - window.dapCalculateEntropy(resolve, reject); - } else { - reject(Error('window.dapCalculateEntropy function is not defined')) - } - }, - - dapGetTokenFromLocalStorage: function(ttl) { - let now = Math.round(Date.now() / 1000.0); // in seconds - let token = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_TOKEN)); - if (item) { - if (now < item.expires_at) { - token = item.token; - } - } - return token; - }, - - 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) { - 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('Akamai-DAP-SS-ID'); - if (dapSSID) { - storage.setDataInLocalStorage(DAP_SS_ID, JSON.stringify(dapSSID)); - } - let deviceId100 = xhr.getResponseHeader('Akamai-DAP-100'); - if (deviceId100 != null) { - 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); - } else { - dapUtils.dapRefreshMembership(ortb2, config, token, onDone); - } - } - }, - function(xhr, status, error, onDone) { - logError('ERROR(' + error + '): failed to retrieve token! ' + status); - onDone() - } - ); - }, - - dapGetMembershipFromLocalStorage: function() { - let now = Math.round(Date.now() / 1000.0); // in seconds - let membership = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_MEMBERSHIP)); - if (item) { - if (now < item.expires_at) { - membership = { - said: item.said, - cohorts: item.cohorts, - attributes: null - }; - } - } - return membership; - }, - - 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) { - item.expires_at = now + DAP_DEFAULT_TOKEN_TTL; - let exp = dapUtils.dapExtractExpiryFromToken(membership.said) - if (typeof exp == 'number') { - item.expires_at = exp - 10; - } - item.said = membership.said; - item.cohorts = membership.cohorts; - 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); - onDone(); - }, - 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 { - onDone(); - } - } - ); - }, - - dapGetEncryptedMembershipFromLocalStorage: function() { - let now = Math.round(Date.now() / 1000.0); // in seconds - let encMembership = null; - let item = JSON.parse(storage.getDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP)); - if (item) { - if (now < item.expires_at) { - encMembership = { - encryptedSegments: item.encryptedSegments - }; - } - } - return encMembership; - }, - - 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) { - 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:'); - dapUtils.dapLog(item); - - 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) { - dapRetryTokenize++; - dapUtils.dapRefreshToken(ortb2, config, true, onDone); - } else { - onDone(); - } - } - ); - }, - - /** - * DESCRIPTION - * Extract expiry value from a token - */ - dapExtractExpiryFromToken: function(token) { - let exp = null; - if (token) { - const tokenArray = token.split('..'); - if (tokenArray && tokenArray.length > 0) { - let decode = atob(tokenArray[0]) - let header = JSON.parse(decode.replace(/"/g, '"')); - exp = header.exp; - } - } - return exp - }, - - /** - * DESCRIPTION - * - * 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) { - let segment = { - name: 'dap.akamai.com', - ext: { - 'segtax': segtax - }, - segment: [] - }; - if (membership != null) { - for (const i of membership.cohorts) { - segment.segment.push({ id: i }); - } - } - let data = { - rtd: { - ortb2: { - user: { - data: [ - segment - ] - }, - site: { - ext: { - data: { - dapSAID: membership.said - } - } - } - } - } - }; - return data; - }, - - /** - * DESCRIPTION - * - * 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) { - let segment = { - name: 'dap.akamai.com', - ext: { - 'segtax': segtax - }, - segment: [] - }; - if (encToken != null) { - segment.segment.push({ id: encToken.encryptedSegments }); - } - let encData = { - rtd: { - ortb2: { - user: { - data: [ - segment - ] - } - } - } - }; - return encData; - }, - - checkAndAddRealtimeData: function(ortb2, data, segtax) { - if (data.rtd) { - if (segtax == 504 && dapUtils.checkIfSegmentsAlreadyExist(ortb2, data.rtd, 504)) { - logMessage('DEBUG(handleInit): rtb Object already added'); - } else { - addRealTimeData(ortb2, data.rtd); - } - logInfo('DEBUG(checkAndAddRealtimeData) - 1'); - } - }, - - 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] - if (element.ext && element.ext.segtax == segtax) { - segmentsExist = true - logMessage('DEBUG(checkIfSegmentsAlreadyExist): rtb Object already added: ', ortb2.user.data); - break; - } - } - } - return segmentsExist - }, - - dapLog: function(args) { - let css = ''; - css += 'display: inline-block;'; - css += 'color: #fff;'; - css += 'background: #F28B20;'; - css += 'padding: 1px 4px;'; - css += 'border-radius: 3px'; - - logInfo('%cDAP Client', css, args); - }, - - isValidHttpsUrl: function(urlString) { - let url; - try { - url = new URL(urlString); - } catch (_) { - return false; - } - return url.protocol === 'https:'; - }, - - checkConsent: function(userConsent) { - let consent = true; - - if (userConsent && userConsent.gdpr && userConsent.gdpr.gdprApplies) { - const gdpr = userConsent.gdpr; - const hasGdpr = (gdpr && typeof gdpr.gdprApplies === 'boolean' && gdpr.gdprApplies) ? 1 : 0; - const gdprConsentString = hasGdpr ? gdpr.consentString : ''; - if (hasGdpr && (!gdprConsentString || gdprConsentString === '')) { - logError('akamaiDapRtd submodule requires consent string to call API'); - consent = false; - } - } else if (userConsent && userConsent.usp) { - const usp = userConsent.usp; - consent = usp[1] !== 'N' && usp[2] !== 'Y'; - } - - return consent; - }, - - /******************************************************************************* - * - * V2 (And Beyond) API - * - ******************************************************************************/ - - /** - * SYNOPSIS - * - * dapTokenize( config, identity ); - * - * DESCRIPTION - * - * Tokenize an identity into a secure, privacy safe pseudonymiziation. - * - * PARAMETERS - * - * config: an array of system configuration parameters - * - * identity: an array of identity parameters passed to the tokenizing system - * - * EXAMPLE - * - * config = { - * api_hostname: "prebid.dap.akadns.net", // required - * domain: "prebid.org", // required - * api_version: "x1", // optional, default "x1" - * }; - * - * token = null; - * identity_email = { - * type: "email", - * identity: "obiwan@jedi.com" - * attributes: { cohorts: [ "100:1641013200", "101:1641013200", "102":3:1641013200" ] }, - * }; - * dap_x1_tokenize( config, identity_email, - * function( response, status, xhr ) { token = response; }, - * function( xhr, status, error ) { ; } // handle error - * - * token = null; - * identity_signature = { type: "signature:1.0.0" }; - * dap_x1_tokenize( config, identity_signature, - * function( response, status, xhr } { token = response; }, - * function( xhr, status, error ) { ; } // handle error - */ - dapTokenize: function(config, identity, onDone, onSuccess = null, onError = null) { - if (onError == null) { - onError = function(xhr, status, error, onDone) {}; - } - - if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); - return; - } - - 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); - return; - } - - if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { - config.api_version = 'x1'; - } - - if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); - return; - } - - if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); - return; - } - - 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); - return; - } - - let apiParams = { - 'type': identity.type, - }; - - if (typeof (identity.identity) != typeof (undefined)) { - apiParams.identity = identity.identity; - } - if (typeof (identity.attributes) != typeof (undefined)) { - apiParams.attributes = identity.attributes; - } - - let entropyDict = JSON.parse(storage.getDataFromLocalStorage(DAP_CLIENT_ENTROPY)); - if (entropyDict && entropyDict.entropy) { - apiParams.entropy = entropyDict.entropy; - } - - let method; - let body; - let path; - switch (config.api_version) { - case 'x1': - case 'x1-dev': - method = 'POST'; - path = '/data-activation/' + config.api_version + '/domain/' + config.domain + '/identity/tokenize'; - body = JSON.stringify(apiParams); - break; - default: - onError(null, 'Invalid api_version: ' + config.api_version, 'ClientError', onDone); - return; - } - - let customHeaders = {'Content-Type': 'application/json'}; - let dapSSID = JSON.parse(storage.getDataFromLocalStorage(DAP_SS_ID)); - if (dapSSID) { - customHeaders['Akamai-DAP-SS-ID'] = dapSSID; - } - - let url = 'https://' + config.api_hostname + path; - let cb = { - success: (response, request) => { - let token = null; - switch (config.api_version) { - case 'x1': - case 'x1-dev': - token = request.getResponseHeader('Akamai-DAP-Token'); - break; - } - onSuccess(token, request.status, request, onDone); - }, - error: (request, error) => { - onError(request, request.statusText, error, onDone); - } - }; - - ajax(url, cb, body, { - method: method, - customHeaders: customHeaders - }); - }, - - /** - * SYNOPSIS - * - * dapMembership( config, token, onSuccess, onError ); - * - * DESCRIPTION - * - * Return the audience segment membership along with a new Secure Advertising - * ID for this token. - * - * PARAMETERS - * - * config: an array of system configuration parameters - * - * token: the token previously returned from the tokenize API - * - * EXAMPLE - * - * config = { - * api_hostname: 'api.dap.akadns.net', - * }; - * - * // token from dap_tokenize - * - * dapMembership( config, token, - * function( membership, status, xhr ) { - * // Run auction with membership.segments and membership.said - * }, - * function( xhr, status, error ) { - * // error - * } ); - * - */ - dapMembership: function(config, token, onDone, onSuccess = null, onError = null) { - if (onError == null) { - onError = function(xhr, status, error, onDone) {}; - } - - if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); - return; - } - - if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { - config.api_version = 'x1'; - } - - if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); - return; - } - - if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); - return; - } - - if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); - return; - } - let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership'; - - let url = 'https://' + config.api_hostname + path; - - let cb = { - success: (response, request) => { - onSuccess(JSON.parse(response), request.status, request, onDone); - }, - error: (error, request) => { - onError(request, request.status, error, onDone); - } - }; - - ajax(url, cb, undefined, { - method: 'GET', - customHeaders: {} - }); - }, - - /** - * SYNOPSIS - * - * dapEncryptedMembership( config, token, onSuccess, onError ); - * - * DESCRIPTION - * - * Return the audience segment membership along with a new Secure Advertising - * ID for this token in encrypted format. - * - * PARAMETERS - * - * config: an array of system configuration parameters - * - * token: the token previously returned from the tokenize API - * - * EXAMPLE - * - * config = { - * api_hostname: 'api.dap.akadns.net', - * }; - * - * // token from dap_tokenize - * - * dapEncryptedMembership( config, token, - * function( membership, status, xhr ) { - * // Run auction with membership.segments and membership.said after decryption - * }, - * function( xhr, status, error ) { - * // error - * } ); - * - */ - dapEncryptedMembership: function(config, token, onDone, onSuccess = null, onError = null) { - if (onError == null) { - onError = function(xhr, status, error, onDone) {}; - } - - if (config == null || typeof (config) == typeof (undefined)) { - onError(null, 'Invalid config object', 'ClientError', onDone); - return; - } - - if (!('api_version' in config) || (typeof (config.api_version) == 'string' && config.api_version.length == 0)) { - config.api_version = 'x1'; - } - - if (typeof (config.api_version) != 'string') { - onError(null, "Invalid api_version: must be a string like 'x1', etc.", 'ClientError', onDone); - return; - } - - if (!(('api_hostname') in config) || typeof (config.api_hostname) != 'string' || config.api_hostname.length == 0) { - onError(null, 'Invalid api_hostname: must be a non-empty string', 'ClientError', onDone); - return; - } - - if (token == null || typeof (token) != 'string') { - onError(null, 'Invalid token: must be a non-null string', 'ClientError', onDone); - return; - } - let path = '/data-activation/' + - config.api_version + - '/token/' + token + - '/membership/encrypt'; - - let url = 'https://' + config.api_hostname + path; - - let cb = { - success: (response, request) => { - let encToken = request.getResponseHeader('Akamai-DAP-Token'); - onSuccess(encToken, request.status, request, onDone); - }, - error: (error, request) => { - onError(request, request.status, error, onDone); - } - }; - ajax(url, cb, undefined, { - method: 'GET', - customHeaders: { - 'Content-Type': 'application/json', - 'Pragma': 'akamai-x-get-extracted-values' - } - }); - } -} +import { + createRtdProvider +} from './symitriDapRtdProvider.js'/* eslint prebid/validate-imports: "off" */ + +export const { + addRealTimeData, + getRealTimeData, + generateRealTimeData, + rtdSubmodule: akamaiDapRtdSubmodule, + storage, + dapUtils, + DAP_TOKEN, + DAP_MEMBERSHIP, + DAP_ENCRYPTED_MEMBERSHIP, + DAP_SS_ID, + DAP_DEFAULT_TOKEN_TTL, + DAP_MAX_RETRY_TOKENIZE, + DAP_CLIENT_ENTROPY +} = createRtdProvider('dap', 'akamaidap', 'Akamai'); 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..df260958104 100644 --- a/modules/amxBidAdapter.js +++ b/modules/amxBidAdapter.js @@ -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/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..cf503dfe006 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,123 @@ 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 = { + 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 +855,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 +1333,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..67557aed10c 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; 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/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/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/azerionedgeRtdProvider.js b/modules/azerionedgeRtdProvider.js index a162ce074aa..852639972c2 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, () => { 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/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/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/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/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/bridBidAdapter.js b/modules/bridBidAdapter.js index f3fe1541886..527cb9d5d5d 100644 --- a/modules/bridBidAdapter.js +++ b/modules/bridBidAdapter.js @@ -1,7 +1,7 @@ -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} from '../libraries/targetVideoUtils/bidderUtils.js' /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -180,50 +180,50 @@ export const spec = { } -/** - * 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; - }; - } - - 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 - }; -} +// /** +// * 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; +// }; +// } + +// 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/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..46393b37a05 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'; @@ -33,6 +34,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 = {}; @@ -58,12 +60,22 @@ 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 function setKeyValue(key) { + 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, RANDOM.toString()); + }); +} + export function sendPageviewEvent(eventType) { if (eventType === 'PAGEVIEW') { window.addEventListener('browsi_pageview', () => { @@ -380,6 +392,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/cadentApertureMXBidAdapter.js b/modules/cadentApertureMXBidAdapter.js index e73564dacdb..97283952888 100644 --- a/modules/cadentApertureMXBidAdapter.js +++ b/modules/cadentApertureMXBidAdapter.js @@ -282,12 +282,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/carodaBidAdapter.js b/modules/carodaBidAdapter.js index cb7b5fbe7c5..8ec260213b6 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'; @@ -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..678002a62b6 100644 --- a/modules/cleanioRtdProvider.js +++ b/modules/cleanioRtdProvider.js @@ -47,6 +47,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'; 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/connatixBidAdapter.js b/modules/connatixBidAdapter.js index 0b840db6c26..6e453e2caa7 100644 --- a/modules/connatixBidAdapter.js +++ b/modules/connatixBidAdapter.js @@ -1,24 +1,41 @@ +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 { 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 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. * If the bid floor cannot be determined, return 0 as a fallback value. @@ -41,10 +58,134 @@ export function getBidFloor(bid) { } } +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 const spec = { code: BIDDER_CODE, gvlid: 143, - supportedMediaTypes: [BANNER], + supportedMediaTypes: [BANNER, VIDEO], /* * Validate the bid request. @@ -55,19 +196,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 +224,7 @@ 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); const requestPayload = { ortb2: bidderRequest.ortb2, @@ -103,10 +235,14 @@ export const spec = { bidRequests, }; + _handleEids(requestPayload, validBidRequests); + + context = requestPayload; + return { method: 'POST', url: AD_URL, - data: requestPayload + data: context }; }, @@ -129,12 +265,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, })); }, @@ -179,7 +316,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..6789c937afb 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 = {}; @@ -246,17 +248,6 @@ function getBidFloor(bidRequest) { 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/contxtfulRtdProvider.js b/modules/contxtfulRtdProvider.js index 6d4b2a2ce29..d4bcd94ff4a 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,86 @@ 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'; -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) { + let value = null; + try { + // Use the Storage Manager + value = storageManager.getDataFromSessionStorage(key, null); + } catch (error) { + } + + return value; +} + +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 +98,10 @@ 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); return true; } catch (error) { logError(MODULE, error); @@ -51,7 +117,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`); @@ -64,87 +130,163 @@ function extractParameters(config) { const hostname = config?.params?.hostname || CONTXTFUL_RECEPTIVITY_DOMAIN; - return {version, customer, hostname}; + return { version, customer, hostname }; } /** * 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 } = 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); + loadExternalScript(CONNECTOR_URL, MODULE_NAME); } /** * 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; - } - - script.addEventListener('initialReceptivity', ({ detail }) => { - let receptivityState = detail?.ReceptivityState; - if (isStr(receptivityState) && !isEmptyStr(receptivityState)) { - initialReceptivity = receptivityState; +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('rxEngineIsReady', ({ detail: api }) => { - contxtfulModule = api; - }); -} - -/** - * Return current receptivity. - * @return { { ReceptivityState: String } } - */ -function getReceptivity() { - return { - ReceptivityState: contxtfulModule?.GetReceptivity()?.ReceptivityState || initialReceptivity - }; + ); } /** * Set targeting data for ad server * @param { [String] } adUnits - * @param {*} _config + * @param {*} config * @param {*} _userConsent - * @return {{ code: { ReceptivityState: String } }} + * @return {{ code: { ReceptivityState: String } }} */ -function getTargetingData(adUnits, _config, _userConsent) { - logInfo(MODULE, 'getTargetingData'); - if (!adUnits) { +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 {}; } +} + +/** + * @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 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; + } - const receptivity = getReceptivity(); - if (!receptivity?.ReceptivityState) { + let fromApiBatched = () => rxApi?.receptivityBatched?.(bidders); + let fromApiSingle = () => prepareBatch(bidders, getRxEngineReceptivity); + let fromStorage = () => prepareBatch(bidders, loadSessionReceptivity); + + function tryMethods(methods) { + for (let method of methods) { + try { + let batch = method(); + if (!isEmpty(batch)) { + return batch; + } + } catch (error) { } + } return {}; } + let rxBatch = {}; + try { + if (isFirstBidRequestCall) { + rxBatch = tryMethods([fromStorage, fromApiBatched, fromApiSingle]); + } else { + rxBatch = tryMethods([fromApiBatched, fromApiSingle, fromStorage]) + } + } catch (error) { } - return adUnits.reduce((targets, code) => { - targets[code] = receptivity; - return targets; - }, {}); -} + if (isEmpty(rxBatch)) { + onReturn(); + return; + } + + bidders + .map((bidderCode) => ({ bidderCode, rx: rxBatch[bidderCode] })) + .filter(({ rx }) => !isEmpty(rx)) + .forEach(({ bidderCode, rx }) => { + const ortb2 = { + user: { + data: [ + { + name: MODULE_NAME, + ext: { + rx, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments?.bidder, { + [bidderCode]: ortb2, + }); + }); + + onReturn(); +}; 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..622b353c27a 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 contact [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 bidders active 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..335b3b3d144 --- /dev/null +++ b/modules/copper6sspBidAdapter.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 = 'copper6ssp'; +const AD_URL = 'https://endpoint.copper6.com/pbjs'; +const SYNC_URL = 'https://сsync.copper6.com'; + +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/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..f8d216f0838 100644 --- a/modules/craftBidAdapter.js +++ b/modules/craftBidAdapter.js @@ -4,7 +4,7 @@ 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'; diff --git a/modules/criteoBidAdapter.js b/modules/criteoBidAdapter.js index c95cbf7af73..bfb703b8a8e 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}`); @@ -125,6 +283,10 @@ export const spec = { if (response.bundle) { saveOnAllStorages(BUNDLE_COOKIE_NAME, response.bundle, GUID_RETENTION_TIME_HOUR); } + + 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 @@ -412,43 +442,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 +497,10 @@ function buildCdbUrl(context) { url += `&optout=1`; } + if (context.networkId) { + url += `&networkId=` + context.networkId; + } + return url; } @@ -499,185 +516,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 +534,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 +544,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 +578,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 +637,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..8ac2b8cbead 100644 --- a/modules/currency.js +++ b/modules/currency.js @@ -28,6 +28,7 @@ export let responseReady = defer(); /** * 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 +62,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 @@ -155,36 +156,35 @@ 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); + 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(); + 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) { 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/dailymotionBidAdapter.js b/modules/dailymotionBidAdapter.js index afded538fd0..04baaebafb9 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 @@ -54,22 +58,61 @@ 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(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 +150,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 +260,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..15e94a5bc36 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'; @@ -234,16 +235,6 @@ export function getBidFloor(bid, mediaType, size, currency) { } } -/** -- 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/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..3ae8b4d6b80 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; @@ -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..c6663434d1e --- /dev/null +++ b/modules/digitalMatterBidAdapter.js @@ -0,0 +1,208 @@ +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 ENDPOINT_URL = 'https://adx.digitalmatter.services/' + +export const spec = { + code: BIDDER_CODE, + 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..d7a28d6b788 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,20 @@ export const spec = { }, }; -function getPayload(bid, userData) { +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 +94,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 +120,22 @@ function getPayload(bid, userData) { organization: organization || '', dob: dob || '', userconsent: 1, + mobile: mobile || '', + pageurl: publisherUrl || '' }; + + 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/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/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/eskimiBidAdapter.js b/modules/eskimiBidAdapter.js index ce01abb9e71..dd3f634462d 100644 --- a/modules/eskimiBidAdapter.js +++ b/modules/eskimiBidAdapter.js @@ -2,16 +2,16 @@ 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'; +import {hasPurpose1Consent} from '../src/utils/gdpr.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 +21,7 @@ const VIDEO_ORTB_PARAMS = [ 'mimes', 'minduration', 'maxduration', - 'placement', + 'plcmt', 'protocols', 'startdelay', 'skip', @@ -37,24 +37,39 @@ 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, + aliases: ['eskimi'], gvlid: GVLID, supportedMediaTypes: [BANNER, VIDEO], 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); @@ -80,7 +95,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) { @@ -102,9 +134,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 +159,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 +182,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 +207,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: bid.params.placementId} if (bidderRequest.gdprConsent) { if (!data.user) data.user = {}; @@ -192,9 +232,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 +245,99 @@ 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) && hasSyncConsent(gdprConsent, uspConsent, gppConsent)) { + let pixelType = syncOptions.iframeEnabled ? 'iframe' : 'image'; + let query = []; + let syncUrl = getUserSyncUrlByRegion(); + // Attaching GDPR Consent Params in UserSync url + if (gdprConsent) { + query.push('gdpr=' + (gdprConsent.gdprApplies & 1)); + query.push('gdpr_consent=' + encodeURIComponent(gdprConsent.consentString || '')); + } + // CCPA + if (uspConsent) { + query.push('us_privacy=' + encodeURIComponent(uspConsent)); + } + // GPP 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('&') : ''}` + }]; + } +} + +function hasSyncConsent(gdprConsent, uspConsent, gppConsent) { + return hasPurpose1Consent(gdprConsent) && hasUspConsent(uspConsent) && hasGppConsent(gppConsent); +} + +function hasUspConsent(uspConsent) { + return typeof uspConsent !== 'string' || !(uspConsent[0] === '1' && uspConsent[2] === 'Y'); +} + +function hasGppConsent(gppConsent) { + return ( + !(gppConsent && Array.isArray(gppConsent.applicableSections)) || + gppConsent.applicableSections.every((section) => typeof section === 'number' && section <= 5) + ); +} + +/** + * 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/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..0ac848bf62a 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.'); } 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..0291ff12ad2 100644 --- a/modules/geoedgeRtdProvider.js +++ b/modules/geoedgeRtdProvider.js @@ -53,6 +53,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 +79,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: { @@ -141,7 +144,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 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..29b9257d325 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; @@ -113,6 +162,10 @@ 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 { useDefaultPreAuction, customPreAuction } = _currentConfig; @@ -122,15 +175,18 @@ 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) { @@ -146,6 +202,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 +217,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..e7423abf115 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, 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,16 @@ 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'); adUnits.forEach((adUnit) => { const matchingAdUnit = findMatchingAdUnit(responseAdUnits, adUnit.code); if (matchingAdUnit) { @@ -86,7 +93,10 @@ function updateAdUnitsBasedOnResponse(adUnits, responseAdUnits, greenbidsId) { keptInAuction: matchingAdUnit.bidders, isExploration: matchingAdUnit.isExploration }); - if (!matchingAdUnit.isExploration) { + if (isFilteringForced) { + adUnit.bids = []; + logInfo('Greenbids Rtd: filtering flag detected, forcing filtering of Rtd module.'); + } else if (!matchingAdUnit.isExploration) { removeFalseBidders(adUnit, matchingAdUnit); } } diff --git a/modules/gridBidAdapter.js b/modules/gridBidAdapter.js index f7db6d878f1..4c1876dfb6f 100644 --- a/modules/gridBidAdapter.js +++ b/modules/gridBidAdapter.js @@ -15,6 +15,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 @@ -441,7 +442,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); @@ -512,17 +513,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..7f48a562177 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'; @@ -290,10 +294,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 +307,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 +320,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 +379,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 +408,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; } @@ -442,11 +447,32 @@ function buildRequests(validBidRequests, bidderRequest) { sizes, url: BID_ENDPOINT, method: 'GET', - data: Object.assign(data, _getBrowserParams(topWindowUrl)) + data: Object.assign(data, _getBrowserParams(topWindowUrl, mosttopLocation)) }); }); 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/hadronRtdProvider.js b/modules/hadronRtdProvider.js index 5c604709b4b..930e0922829 100644 --- a/modules/hadronRtdProvider.js +++ b/modules/hadronRtdProvider.js @@ -24,7 +24,7 @@ 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 HADRONID_LOCAL_NAME = 'auHadronId'; export const RTD_LOCAL_NAME = 'auHadronRtd'; export const storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); @@ -132,7 +132,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); } @@ -165,9 +164,9 @@ export function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { } } - const userIds = typeof getGlobal().getUserIds === 'function' ? (getGlobal()).getUserIds() : {}; + const userIds = {}; - let hadronId = storage.getDataFromLocalStorage(HALOID_LOCAL_NAME); + let hadronId = storage.getDataFromLocalStorage(HADRONID_LOCAL_NAME); if (isStr(hadronId)) { if (typeof getGlobal().refreshUserIds === 'function') { (getGlobal()).refreshUserIds({submoduleNames: 'hadronId'}); diff --git a/modules/holidBidAdapter.js b/modules/holidBidAdapter.js index f046c860562..c72d21d08b4 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, @@ -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..7d1dc1a33c1 --- /dev/null +++ b/modules/humansecurityRtdProvider.js @@ -0,0 +1,179 @@ +/** + * 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'; + +/** + * @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, 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..d08efdcb232 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); @@ -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/improvedigitalBidAdapter.js b/modules/improvedigitalBidAdapter.js index fbceeee6d90..ddf13caa833 100644 --- a/modules/improvedigitalBidAdapter.js +++ b/modules/improvedigitalBidAdapter.js @@ -3,8 +3,14 @@ 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'; +/** + * 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'; /** @@ -25,11 +31,7 @@ const EXTEND_URL = 'https://pbs.360yield.com/openrtb2/auction'; const IFRAME_SYNC_URL = 'https://hb.360yield.com/prebid-universal-creative/load-cookie.html'; const VIDEO_PARAMS = { - DEFAULT_MIMES: ['video/mp4'], - PLACEMENT_TYPE: { - INSTREAM: 1, - OUTSTREAM: 3, - } + DEFAULT_MIMES: ['video/mp4'] }; export const spec = { @@ -46,7 +48,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); }, /** @@ -140,14 +142,11 @@ export const CONVERTER = ortbConverter({ } 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 +209,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 +231,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)) - ); - } - } } } } 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/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..76615730dd5 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.js @@ -0,0 +1,256 @@ +import { logInfo, logError, getWindowSelf, getWindowTop, getWindowLocation } 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/detectBrowserUtils/detectBrowserUtils.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 FIRST_PARTY_KEY = '_iiq_fdata'; +const FIRST_PARTY_DATA_KEY = '_iiq_fdata'; +const JSVERSION = 0.2 + +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' +}; + +let iiqAnalyticsAnalyticsAdapter = Object.assign(adapter({ defaultUrl, analyticsType }), { + initOptions: { + lsValueInitialized: false, + partner: null, + fpid: null, + currentGroup: null, + dataInLs: null, + eidl: null, + lsIdsInitialized: false, + manualReport: false + }, + track({ eventType, args }) { + switch (eventType) { + case BID_WON: + bidWon(args); + break; + default: + break; + } + } +}); + +// Events needed +const { + BID_WON +} = 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() : ''; + } +} + +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; + } + let iData = readData(FIRST_PARTY_DATA_KEY + '_' + iiqAnalyticsAnalyticsAdapter.initOptions.partner); + if (iData) { + iiqAnalyticsAnalyticsAdapter.initOptions.lsIdsInitialized = true; + let pData = JSON.parse(iData); + iiqAnalyticsAnalyticsAdapter.initOptions.dataInLs = pData.data; + iiqAnalyticsAnalyticsAdapter.initOptions.eidl = pData.eidl || -1; + } + } catch (e) { + logError(e) + } +} + +function bidWon(args) { + 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 (!iiqAnalyticsAnalyticsAdapter.initOptions.manualReport) { + ajax(constructFullUrl(preparePayload(args, true)), undefined, null, { method: 'GET' }); + } + + logInfo('IIQ ANALYTICS -> BID WON') +} + +function getRandom(start, end) { + return Math.floor((Math.random() * (end - start + 1)) + start); +} + +export function preparePayload(data) { + let result = getDefaultDataObject(); + + result[PARAMS_NAMES.partnerId] = iiqAnalyticsAnalyticsAdapter.initOptions.partner; + result[PARAMS_NAMES.prebidVersion] = prebidVersion; + result[PARAMS_NAMES.referrer] = getReferrer(); + + result[PARAMS_NAMES.abTestGroup] = iiqAnalyticsAnalyticsAdapter.initOptions.currentGroup; + + 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) + + fillPrebidEventData(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 fillPrebidEventData(eventData, result) { + if (eventData.bidderCode) { result.bidderCode = eventData.bidderCode; } + if (eventData.cpm) { result.cpm = eventData.cpm; } + if (eventData.currency) { result.currency = eventData.currency; } + if (eventData.originalCpm) { result.originalCpm = eventData.originalCpm; } + if (eventData.originalCurrency) { result.originalCurrency = eventData.originalCurrency; } + if (eventData.status) { result.status = eventData.status; } + if (eventData.auctionId) { result.prebidAuctionId = eventData.auctionId; } + + result.biddingPlatformId = 1; + result.partnerAuctionId = 'BW'; +} + +function getDefaultDataObject() { + return { + 'inbbl': false, + 'pbjsver': prebidVersion, + 'partnerAuctionId': 'BW', + 'reportSource': 'pbjs', + 'abGroup': 'U', + 'jsversion': JSVERSION, + 'partnerId': -1, + 'biddingPlatformId': 1, + 'idls': false, + 'ast': -1, + 'aeidln': -1 + } +} + +function constructFullUrl(data) { + let report = [] + data = btoa(JSON.stringify(data)) + report.push(data) + return defaultUrl + '?pid=' + iiqAnalyticsAnalyticsAdapter.initOptions.partner + + '&mct=1' + + ((iiqAnalyticsAnalyticsAdapter.initOptions && iiqAnalyticsAnalyticsAdapter.initOptions.fpid) + ? '&iiqid=' + encodeURIComponent(iiqAnalyticsAnalyticsAdapter.initOptions.fpid.pcid) : '') + + '&agid=' + REPORTER_ID + + '&jsver=' + JSVERSION + + '&vrref=' + getReferrer() + + '&source=pbjs' + + '&payload=' + JSON.stringify(report) +} + +export function getReferrer() { + try { + if (getWindowSelf() === getWindowTop()) { + return getWindowLocation().href; + } else { + return getWindowTop().location.href; + } + } catch (error) { + logError(`Error accessing location: ${error}`); + return ''; + } +} + +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..905d88a9b21 --- /dev/null +++ b/modules/intentIqAnalyticsAdapter.md @@ -0,0 +1,27 @@ +# 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) + +No additional configuration for this module is required. We will use the configuration provided for Intent IQ Universal IQ module. + +#### Example Configuration + +```js +pbjs.enableAnalytics({ + provider: 'iiqAnalytics' +}); +``` diff --git a/modules/intentIqIdSystem.js b/modules/intentIqIdSystem.js index 109ea5c39c6..2702aa9848d 100644 --- a/modules/intentIqIdSystem.js +++ b/modules/intentIqIdSystem.js @@ -10,6 +10,10 @@ 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 { gppDataHandler, uspDataHandler } from '../src/consentHandler.js'; +import AES from 'crypto-js/aes.js'; +import Utf8 from 'crypto-js/enc-utf8.js'; +import { detectBrowser } from '../libraries/detectBrowserUtils/detectBrowserUtils.js'; /** * @typedef {import('../modules/userId/index.js').Submodule} Submodule @@ -21,11 +25,31 @@ 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 }); +export let FIRST_PARTY_DATA_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.2 +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 +66,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 +103,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 +125,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 +157,54 @@ function tryParse(data) { } } +/** + * Convert GPP data to an object + * @param {Object} data + * @return {string} The JSON string representation of the input data. + */ +export function handleGPPData(data = {}) { + if (Array.isArray(data)) { + let obj = {}; + for (const element of data) { + obj = Object.assign(obj, element); + } + return JSON.stringify(obj); + } + return JSON.stringify(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 = { /** @@ -120,25 +228,136 @@ 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 = {} + + const firePartnerCallback = () => { + if (configParams.callback && !callbackFired) { + callbackFired = true; + if (callbackTimeoutID) clearTimeout(callbackTimeoutID); + 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; } let rrttStrtTime = 0; + let partnerData = {}; + let shouldCallServer = false + + const currentBrowserLowerCase = detectBrowser(); + const browserBlackList = typeof configParams.browserBlackList === 'string' ? configParams.browserBlackList.toLowerCase() : ''; + const allowedStorage = defineStorageType(config.enabledStorageTypes); + + // 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 = gppDataHandler.getConsentData(); + + if (uspData) { + cmpData.us_privacy = uspData; + } + + if (gppData) { + cmpData.gpp = ''; + cmpData.gpi = 1; + + if (gppData.parsedSections && 'usnat' in gppData.parsedSections) { + cmpData.gpp = handleGPPData(gppData.parsedSections['usnat']); + cmpData.gpi = 0 + } + cmpData.gpp_sid = gppData.applicableSections; + } + + // Read client hints from storage + let clientHints = readData(CLIENT_HINTS_KEY, allowedStorage); + + // 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 (!FIRST_PARTY_DATA_KEY.includes(configParams.partner)) { + FIRST_PARTY_DATA_KEY += '_' + configParams.partner; + } // 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) { + let firstPartyData = tryParse(readData(FIRST_PARTY_KEY, 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.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_value !== cmpData.gpp) { + firstPartyData.uspapi_value = cmpData.us_privacy; + firstPartyData.gpp_value = cmpData.gpp; + firstPartyData.isOptedOut = false + firstPartyData.cttl = 0 + shouldCallServer = true; + partnerData.data = {} + 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 && Object.keys(runtimeEids).length)) { + firePartnerCallback() + } + + if (!shouldCallServer) { + firePartnerCallback(); + return { id: runtimeEids }; + } // 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,42 +368,97 @@ 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 ? '&gpv=' + 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) : ''; + + const storeFirstPartyData = () => { + 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 = {}; + removeDataByKey(FIRST_PARTY_DATA_KEY, allowedStorage) + firePartnerCallback() + callback() } + 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 (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.eids + callback(respJson.data.eids); + firePartnerCallback() + const encryptedData = encryptData(JSON.stringify(respJson.data.eids)) + partnerData.data = encryptedData; + } else { + callback(); + firePartnerCallback() } - callback(respJson.data); + storeFirstPartyData(); } else { callback(); + firePartnerCallback() } }, error: error => { @@ -192,18 +466,32 @@ export const intentIqIdSubmodule = { callback(); } }; - 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 }); - } + + ajax(url, callbacks, undefined, { method: 'GET', withCredentials: true }); }; - return { callback: resp }; + const respObj = { callback: resp }; + if (runtimeEids?.length) respObj.id = runtimeEids; + 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..8ddbaf08f60 --- /dev/null +++ b/modules/intentIqIdSystem.md @@ -0,0 +1,67 @@ +``` +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"` | + +### 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() + }, + 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..8d2f0a7734d 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'; @@ -80,7 +80,7 @@ function parseRequestPrebidjsToOpenRTB(prebidRequest, bidderRequest) { 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,36 +89,36 @@ 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.tagid = bid.adUnitCode; @@ -131,7 +131,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 +156,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/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..c03d579d2a5 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 - } -} - 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/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..8429228d7af 100644 --- a/modules/kargoBidAdapter.js +++ b/modules/kargoBidAdapter.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 diff --git a/modules/kimberliteBidAdapter.js b/modules/kimberliteBidAdapter.js index 72df921e18f..6ad8b9eda05 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 {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,32 @@ 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; + } + + 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 +72,10 @@ export const spec = { return { method: METHOD, url: ENDPOINT_URL, - data: converter.toORTB({ bidderRequest, bidRequests }) + data: converter.toORTB({ + bidRequests, + bidderRequest + }) } }, 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..3ef40c8a921 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); 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/kueezBidAdapter.js b/modules/kueezBidAdapter.js index 5a5536e0c1a..63a01bfea02 100644 --- a/modules/kueezBidAdapter.js +++ b/modules/kueezBidAdapter.js @@ -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/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..50a8dc2aa1d 100644 --- a/modules/liveIntentIdSystem.js +++ b/modules/liveIntentIdSystem.js @@ -7,12 +7,12 @@ 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 { LiveConnect } from 'live-connect-js/prebid'; // eslint-disable-line prebid/validate-imports +import { gdprDataHandler, uspDataHandler, gppDataHandler, coppaDataHandler } from '../src/adapterManager.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { UID2_EIDS } from '../libraries/uid2Eids/uid2Eids.js'; import {UID1_EIDS} from '../libraries/uid1Eids/uid1Eids.js'; -import {UID2_EIDS} from '../libraries/uid2Eids/uid2Eids.js'; import { getRefererInfo } from '../src/refererDetection.js'; /** @@ -20,13 +20,13 @@ import { getRefererInfo } from '../src/refererDetection.js'; * @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 GVLID = 148; +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 defaultRequestedAttributes = {'nonId': true}; const calls = { ajaxGet: (url, onSuccess, onError, timeout) => { ajaxBuilder(timeout)( @@ -53,10 +53,10 @@ let liveConnect = null; */ export function reset() { if (window && window.liQ_instances) { - window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)) + window.liQ_instances.forEach(i => i.eventBus.off(EVENTS_TOPIC, setEventFiredFlag)); window.liQ_instances = []; } - liveIntentIdSubmodule.setModuleMode(null) + liveIntentIdSubmodule.setModuleMode(null); eventFired = false; liveConnect = null; } @@ -70,7 +70,7 @@ export function setEventFiredFlag() { function parseLiveIntentCollectorConfig(collectConfig) { const config = {}; - collectConfig = collectConfig || {} + collectConfig = collectConfig || {}; collectConfig.appId && (config.appId = collectConfig.appId); collectConfig.fpiStorageStrategy && (config.storageStrategy = collectConfig.fpiStorageStrategy); collectConfig.fpiExpirationDays && (config.expirationDays = collectConfig.fpiExpirationDays); @@ -86,30 +86,39 @@ function parseLiveIntentCollectorConfig(collectConfig) { * @returns {Array} */ 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) ? [k] : []); + return Object.entries(config).flatMap(([k, v]) => (typeof v === 'boolean' && v) ? [renameAttribute(k)] : []); } if (typeof overrides === 'object') { - return createParameterArray({...defaultRequestedAttributes, ...overrides}) + return createParameterArray({...defaultRequestedAttributes, ...overrides}); } else { return createParameterArray(defaultRequestedAttributes); } } function initializeLiveConnect(configParams) { - configParams = configParams || {}; if (liveConnect) { return liveConnect; } + configParams = configParams || {}; + const fpidConfig = configParams.fpid || {}; + const publisherId = configParams.publisherId || 'any'; const identityResolutionConfig = { publisherId: publisherId, requestedAttributes: parseRequestedAttributes(configParams.requestedAttributesOverrides) }; if (configParams.url) { - identityResolutionConfig.url = configParams.url - } + identityResolutionConfig.url = configParams.url; + }; identityResolutionConfig.ajaxTimeout = configParams.ajaxTimeout || DEFAULT_AJAX_TIMEOUT; @@ -119,7 +128,7 @@ function initializeLiveConnect(configParams) { liveConnectConfig.distributorId = configParams.distributorId; identityResolutionConfig.source = configParams.distributorId; } else { - identityResolutionConfig.source = configParams.partner || 'prebid' + identityResolutionConfig.source = configParams.partner || 'prebid'; } liveConnectConfig.wrapperName = 'prebid'; @@ -127,11 +136,16 @@ function initializeLiveConnect(configParams) { 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() + const gdprConsent = gdprDataHandler.getConsentData(); if (gdprConsent) { liveConnectConfig.gdprApplies = gdprConsent.gdprApplies; liveConnectConfig.gdprConsent = gdprConsent.consentString; @@ -145,21 +159,21 @@ function initializeLiveConnect(configParams) { // 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 }) + liveConnect.push({ hash: configParams.emailHash }); } return liveConnect; } function tryFireEvent() { if (!eventFired && liveConnect) { - const eventDelay = liveConnect.config.fireEventDelay || 500 + const eventDelay = liveConnect.config.fireEventDelay || 500; setTimeout(() => { - const instances = window.liQ_instances - instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)) + const instances = window.liQ_instances; + instances.forEach(i => i.eventBus.once(EVENTS_TOPIC, setEventFiredFlag)); if (!eventFired && liveConnect) { liveConnect.fire(); } - }, eventDelay) + }, eventDelay); } } @@ -171,12 +185,12 @@ export const liveIntentIdSubmodule = { * @type {string} */ name: MODULE_NAME, - + gvlid: GVLID, setModuleMode(mode) { - this.moduleMode = mode + this.moduleMode = mode; }, getInitializer() { - return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode) + return (liveConnectConfig, storage, calls) => LiveConnect(liveConnectConfig, storage, calls, this.moduleMode); }, /** @@ -194,46 +208,54 @@ export const liveIntentIdSubmodule = { const result = {}; // old versions stored lipbid in unifiedId. Ensure that we can still read the data. - const lipbid = value.nonId || value.unifiedId + const lipbid = value.nonId || value.unifiedId; if (lipbid) { - value.lipbid = lipbid - delete value.unifiedId - result.lipb = value + 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 } } + result.uid2 = { 'id': value.uid2, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.bidswitch) { - result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } } + result.bidswitch = { 'id': value.bidswitch, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.medianet) { - result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } } + result.medianet = { 'id': value.medianet, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.magnite) { - result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } } + result.magnite = { 'id': value.magnite, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.index) { - result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } } + result.index = { 'id': value.index, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.openx) { - result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } } + result.openx = { 'id': value.openx, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.pubmatic) { - result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } } + result.pubmatic = { 'id': value.pubmatic, ext: { provider: LI_PROVIDER_DOMAIN } }; } if (value.sovrn) { - result.sovrn = { 'id': value.sovrn, ext: { provider: LI_PROVIDER_DOMAIN } } + 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) { @@ -280,6 +302,7 @@ export const liveIntentIdSubmodule = { return { callback: result }; }, + primaryIds: ['libp'], eids: { ...UID1_EIDS, ...UID2_EIDS, @@ -380,6 +403,13 @@ export const liveIntentIdSubmodule = { return data.ext; } } + }, + 'fpid': { + source: 'fpid.liveintent.com', + atype: 1, + getValue: function(data) { + return data.id; + } } } }; 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/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..447257f97da 100755 --- a/modules/luponmediaBidAdapter.js +++ b/modules/luponmediaBidAdapter.js @@ -9,12 +9,13 @@ import { 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 +221,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 +273,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; } @@ -469,9 +449,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..dd16baed66e 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 { @@ -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(); 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/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/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..d62cb789ea4 100644 --- a/modules/mediaimpactBidAdapter.js +++ b/modules/mediaimpactBidAdapter.js @@ -115,12 +115,12 @@ export const spec = { creativeId: ad.creativeId, netRevenue: ad.netRevenue, currency: ad.currency, - winNotification: ad.winNotification - } + winNotification: ad.winNotification, + meta: {} + }; - bidObject.meta = {}; - if (ad.adomain && ad.adomain.length > 0) { - bidObject.meta.advertiserDomains = ad.adomain; + if (ad.meta) { + bidObject.meta = ad.meta; } return bidObject; diff --git a/modules/mediakeysBidAdapter.js b/modules/mediakeysBidAdapter.js index f4967fed170..987b2689f6b 100644 --- a/modules/mediakeysBidAdapter.js +++ b/modules/mediakeysBidAdapter.js @@ -66,6 +66,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), diff --git a/modules/medianetAnalyticsAdapter.js b/modules/medianetAnalyticsAdapter.js index 68927cc6b13..e8d73e77d5f 100644 --- a/modules/medianetAnalyticsAdapter.js +++ b/modules/medianetAnalyticsAdapter.js @@ -36,8 +36,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'; diff --git a/modules/medianetBidAdapter.js b/modules/medianetBidAdapter.js index c2c792ec866..c90292a74ac 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,7 @@ 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'; /** * @typedef {import('../src/adapters/bidderFactory.js').BidRequest} BidRequest @@ -35,27 +38,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 +81,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) { @@ -186,7 +182,7 @@ function extParams(bidRequest, bidderRequests) { const coppaApplies = !!(config.getConfig('coppa')); 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 }, @@ -199,7 +195,7 @@ function extParams(bidRequest, bidderRequests) { ); } -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 +257,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; } @@ -336,14 +334,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 +368,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 logEvent(event, data) { + const logData = getLoggingData(event, data); + fireAjaxLog(EVENT_PIXEL_URL, formatQS(logData)); } -function clearMnData() { - mnData = {}; +function clearPageMeta() { + pageMeta = undefined; } function addRenderer(bid) { @@ -481,26 +521,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 +588,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/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/minutemediaplusBidAdapter.md b/modules/minutemediaplusBidAdapter.md deleted file mode 100644 index 410c00e7017..00000000000 --- a/modules/minutemediaplusBidAdapter.md +++ /dev/null @@ -1,35 +0,0 @@ -# Overview - -**Module Name:** MinuteMediaPlus Bidder Adapter - -**Module Type:** Bidder Adapter - -**Maintainer:** hb@minutemedia.com - -# Description - -Module that connects to MinuteMediaPlus's demand sources. - -# Test Parameters -```js -var adUnits = [ - { - code: 'test-ad', - sizes: [[300, 250]], - bids: [ - { - bidder: 'mmplus', - params: { - cId: '562524b21b1c1f08117fc7f9', - pId: '59ac17c192832d0011283fe3', - bidFloor: 0.0001, - ext: { - param1: 'loremipsum', - param2: 'dolorsitamet' - } - } - } - ] - } -]; -``` 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..8f08b21e14a --- /dev/null +++ b/modules/mobianRtdProvider.js @@ -0,0 +1,85 @@ +/** + * 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 { deepSetValue, safeJSONParse } from '../src/utils.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ + +export const MOBIAN_URL = 'https://prebid.outcomes.net/api/prebid/v1/assessment/async'; + +/** @type {RtdSubmodule} */ +export const mobianBrandSafetySubmodule = { + name: 'mobianBrandSafety', + init: init, + getBidRequestData: getBidRequestData +}; + +function init() { + return true; +} + +function getBidRequestData(bidReqConfig, callback, config) { + const { site: ortb2Site } = bidReqConfig.ortb2Fragments.global; + const pageUrl = encodeURIComponent(getPageUrl()); + const requestUrl = `${MOBIAN_URL}?url=${pageUrl}`; + + const ajax = ajaxBuilder(); + + return new Promise((resolve) => { + ajax(requestUrl, { + success: function(responseData) { + let response = safeJSONParse(responseData); + if (!response || !response.meta.has_results) { + resolve({}); + callback(); + return; + } + + const results = response.results; + const mobianRisk = results.mobianRisk || 'unknown'; + const contentCategories = results.mobianContentCategories || []; + const sentiment = results.mobianSentiment || 'unknown'; + const emotions = results.mobianEmotions || []; + const themes = results.mobianThemes || []; + const tones = results.mobianTones || []; + const genres = results.mobianGenres || []; + + const risk = { + risk: mobianRisk, + contentCategories: contentCategories, + sentiment: sentiment, + emotions: emotions, + themes: themes, + tones: tones, + genres: genres, + }; + + deepSetValue(ortb2Site.ext, 'data.mobianRisk', mobianRisk); + deepSetValue(ortb2Site.ext, 'data.mobianContentCategories', contentCategories); + deepSetValue(ortb2Site.ext, 'data.mobianSentiment', sentiment); + deepSetValue(ortb2Site.ext, 'data.mobianEmotions', emotions); + deepSetValue(ortb2Site.ext, 'data.mobianThemes', themes); + deepSetValue(ortb2Site.ext, 'data.mobianTones', tones); + deepSetValue(ortb2Site.ext, 'data.mobianGenres', genres); + + resolve(risk); + callback(); + }, + error: function () { + resolve({}); + callback(); + } + }); + }); +} + +function getPageUrl() { + return window.location.href; +} + +submodule('realTimeData', mobianBrandSafetySubmodule); diff --git a/modules/mobianRtdProvider.md b/modules/mobianRtdProvider.md new file mode 100644 index 00000000000..e7475eb40fb --- /dev/null +++ b/modules/mobianRtdProvider.md @@ -0,0 +1,11 @@ +# 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. 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..c9da876b292 100644 --- a/modules/nativoBidAdapter.js +++ b/modules/nativoBidAdapter.js @@ -34,7 +34,7 @@ const localPbjsRef = getGlobal() * 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 = [] 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..9dbf2e93dc6 100644 --- a/modules/nextMillenniumBidAdapter.js +++ b/modules/nextMillenniumBidAdapter.js @@ -1,6 +1,5 @@ import { _each, - createTrackPixelHtml, deepAccess, deepSetValue, getBidIdParameter, @@ -12,8 +11,8 @@ import { parseUrl, triggerPixel, } from '../src/utils.js'; +import {getAd} from '../libraries/targetVideoUtils/bidderUtils.js'; -import {getGlobal} from '../src/prebidGlobal.js'; import { EVENTS } from '../src/constants.js'; import {BANNER, VIDEO} from '../src/mediaTypes.js'; import {config} from '../src/config.js'; @@ -21,7 +20,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.0.0'; +const PBJS_VERSION = 'v$prebid.version$'; const GVLID = 1060; const BIDDER_CODE = 'nextMillennium'; const ENDPOINT = 'https://pbs.nextmillmedia.com/openrtb2/auction'; @@ -81,65 +81,46 @@ export const spec = { buildRequests: function(validBidRequests, bidderRequest) { const requests = []; window.nmmRefreshCounts = window.nmmRefreshCounts || {}; + const site = getSiteObj(); + const device = getDeviceObj(); - _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, + ext: { + next_mil_imps: [], + }, - const postBody = { - id: bidderRequest?.bidderRequestId, - cur, - ext: { - prebid: { - storedrequest: { - id, - }, - }, + device, + site, + 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; @@ -149,16 +130,15 @@ export const spec = { 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 {ad, adUrl, vastUrl, vastXml} = getAd(bid); const bidResponse = { requestId, - params, cpm: bid.price, width: bid.w, height: bid.h, @@ -182,11 +162,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 +208,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 +238,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,6 +252,21 @@ export const spec = { }, }; +function getExtNextMilImp(bid) { + if (typeof window?.nmmRefreshCounts[bid.adUnitCode] === 'number') ++window.nmmRefreshCounts[bid.adUnitCode]; + const nextMilImp = { + impId: bid.adUnitCode, + 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 = { @@ -362,10 +361,16 @@ export function setOrtb2Parameters(postBody, ortb2 = {}) { } } -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 = '') { @@ -411,21 +416,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 +424,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 +446,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()) || {}; 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..69032fb151b 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,7 @@ const ALIASES = [ { code: 'league-m', gvlid: 965 }, { code: 'prjads' }, { code: 'pubtech' }, + { code: '1accord', gvlid: 965 }, ]; export const storage = getStorageManager({ @@ -69,6 +70,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 +121,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/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..4865ea3bc9c 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 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..c33fccdbac9 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} 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 } }); 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..8ddcb2c3980 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; } 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..4e639fb88ee 100644 --- a/modules/optableBidAdapter.js +++ b/modules/optableBidAdapter.js @@ -35,7 +35,7 @@ export const spec = { return { bidId: impid, config } }) - return { bids, fledgeAuctionConfigs: auctionConfigs } + return { bids, paapi: auctionConfigs } }, supportedMediaTypes: [BANNER] } 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..5ce514a46d7 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} 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,33 +342,11 @@ 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, 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..d3ba7fc0792 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', @@ -444,7 +502,7 @@ export const spec = { if (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..02fb38624f8 100644 --- a/modules/paapi.js +++ b/modules/paapi.js @@ -2,15 +2,15 @@ * 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 {deepSetValue, logInfo, logWarn, mergeDeep, sizesToSizeTuples, deepAccess, deepEqual} 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'; const MODULE = 'PAAPI'; @@ -19,32 +19,23 @@ 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); - }; -} +const pendingConfigsForAuction = auctionStore(); +const configsForAuction = auctionStore(); +const pendingBuyersForAuction = 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,10 +43,7 @@ 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) { moduleConfig = cfg; logInfo(`${MODULE} enabled (browser ${isFledgeSupported() ? 'supports' : 'does NOT support'} runAdAuction)`, cfg); @@ -65,11 +53,12 @@ export function init(cfg, configNamespace) { } } -getHook('addComponentAuction').before(addComponentAuctionHook); +getHook('addPaapiConfig').before(addPaapiConfigHook); +getHook('makeBidRequests').before(addPaapiData); getHook('makeBidRequests').after(markForFledge); events.on(EVENTS.AUCTION_END, onAuctionEnd); -function getSlotSignals(bidsReceived = [], bidRequests = []) { +function getSlotSignals(adUnit = {}, bidsReceived = [], bidRequests = []) { let bidfloor, bidfloorcur; if (bidsReceived.length > 0) { const bestBid = bidsReceived.reduce(maximum(currencyCompare(bid => [bid.cpm, bid.currency]))); @@ -86,36 +75,52 @@ function getSlotSignals(bidsReceived = [], bidRequests = []) { deepSetValue(cfg, 'auctionSignals.prebid.bidfloor', bidfloor); bidfloorcur && deepSetValue(cfg, 'auctionSignals.prebid.bidfloorcur', bidfloorcur); } + const requestedSize = getRequestedSize(adUnit); + if (requestedSize) { + cfg.requestedSize = requestedSize; + } 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 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 = {}; (adUnitCodes || []).forEach(au => { paapiConfigs[au] = null; !latestAuctionForAdUnit.hasOwnProperty(au) && (latestAuctionForAdUnit[au] = null); }); - Object.entries(pendingForAuction(auctionId) || {}).forEach(([adUnitCode, auctionConfigs]) => { + const pendingConfigs = pendingConfigsForAuction(auctionId); + const pendingBuyers = pendingBuyersForAuction(auctionId); + if (pendingConfigs && pendingBuyers) { + Object.entries(pendingBuyers).forEach(([adUnitCode, igbRequests]) => { + buyersToAuctionConfigs(igbRequests).forEach(auctionConfig => append(pendingConfigs, adUnitCode, auctionConfig)) + }) + } + Object.entries(pendingConfigs || {}).forEach(([adUnitCode, auctionConfigs]) => { const forThisAdUnit = (bid) => bid.adUnitCode === adUnitCode; - const slotSignals = getSlotSignals(bidsReceived?.filter(forThisAdUnit), allReqs?.filter(forThisAdUnit)); + const slotSignals = getSlotSignals(adUnitsByCode[adUnitCode], 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], - }; - } latestAuctionForAdUnit[adUnitCode] = auctionId; }); configsForAuction(auctionId, paapiConfigs); @@ -126,52 +131,166 @@ function onAuctionEnd({auctionId, bidsReceived, bidderRequests, adUnitCodes, adU ); } -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; +} + +export function addPaapiConfigHook(next, request, paapiConfig) { + if (getFledgeConfig(config.getCurrentBidder()).enabled) { + const {adUnitCode, auctionId} = 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, config); + } + if (igb && checkOrigin(igb)) { + igb.pbs = setFPD(igb.pbs || {}, request); + storePendingData(pendingBuyersForAuction, [request, igb]) } } - next(request, componentAuctionConfig); + 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]; + } + 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,24 +302,82 @@ 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 + } }); }); } @@ -208,48 +385,78 @@ export function markForFledge(next, bidderRequests) { } 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/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..859bfc9de7e 100644 --- a/modules/pgamsspBidAdapter.js +++ b/modules/pgamsspBidAdapter.js @@ -1,253 +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 = 'pgamssp'; 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, 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..a1bad2d69ba 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,7 +24,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', noP1Consent: 'https://prebid-server.rubiconproject.com/cookie_sync', }, - timeout: 500 + maxTimeout: 500 }, 'openx': { adapter: 'prebidServer', @@ -37,7 +37,7 @@ export const S2S_VENDORS = { p1Consent: 'https://prebid.openx.net/cookie_sync', noP1Consent: 'https://prebid.openx.net/cookie_sync' }, - timeout: 1000 + maxTimeout: 1000 }, 'openwrap': { adapter: 'prebidServer', @@ -46,6 +46,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..31ea363c9df 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'; @@ -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..6f6ab82eca2 100644 --- a/modules/precisoBidAdapter.js +++ b/modules/precisoBidAdapter.js @@ -1,212 +1,75 @@ -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 } from '../src/mediaTypes.js'; +import { getStorageManager } from '../src/storageManager.js'; +import { MODULE_TYPE_UID } from '../src/activities/modules.js'; +import { buildRequests, interpretResponse, 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 }); +// export const storage2 = getStorageManager({ moduleType: MODULE_TYPE_UID, moduleName: BIDDER__CODE }); +const SUPPORTED_MEDIA_TYPES = [BANNER]; 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` - }); + buildRequests: buildRequests(endpoint), + interpretResponse, + onBidWon, + getUserSyncs: (syncOptions, serverResponses, gdprConsent, uspConsent) => { + const isSpec = syncOptions.spec; + if (!Object.is(isSpec, true)) { + let syncId = storage.getCookie('_sharedid'); + syncEndpoint = syncEndpoint + 'id=' + syncId; } else { - syncs.push({ - type: 'image', - url: `${URL_SYNC}&gdpr=${gdprApplies ? 1 : 0}&gdpr_consent=${consentString}&us_privacy=${uspConsent}&t=2` - }); + syncEndpoint = syncEndpoint + 'id=NA'; } - return syncs + return buildUserSyncs(syncOptions, serverResponses, gdprConsent, uspConsent, syncEndpoint); } - }; -function getCountryCodeByTimezone(city) { - 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'); +registerBidder(spec); - if (timeZoneName) { - // Extract the country code from the timezone name - const parts = timeZoneName.value.split('-'); - if (parts.length >= 2) { - return parts[1]; - } - } - } catch (error) { - // Handle errors, such as an invalid timezone city - logInfo(error); - } +async function getapi(url) { + try { + // Storing response + const response = await fetch(url); - // Handle the case where the city is not found or an error occurred - return 'Unknown'; -} + // Storing data in form of JSON + var data = await response.json(); -function getBidFloor(bid) { - if (!isFn(bid.getFloor)) { - return deepAccess(bid, 'params.bidFloor', 0); - } + const dataMap = new Map(Object.entries(data)); + const uuidValue = dataMap.get('UUID'); - try { - const bidFloor = bid.getFloor({ - currency: 'USD', - mediaType: '*', - size: '*', - }); - return bidFloor.floor; - } catch (_) { - return 0 + if (!Object.is(uuidValue, null) && !Object.is(uuidValue, undefined)) { + storage.setDataInLocalStorage('_pre|id', uuidValue); + } + return data; + } catch (error) { + logInfo('Error in preciso precall' + error); } } - -registerBidder(spec); 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..617123746e5 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]; @@ -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..561878f7cbd 100644 --- a/modules/pubmaticBidAdapter.js +++ b/modules/pubmaticBidAdapter.js @@ -31,7 +31,7 @@ const DEFAULT_HEIGHT = 0; const PREBID_NATIVE_HELP_LINK = 'http://prebid.org/dev-docs/show-native-ads.html'; const PUBLICATION = 'pubmatic'; // Your publication on Blue Billywig, potentially with environment (e.g. publication.bbvms.com or publication.test.bbvms.com) const RENDERER_URL = 'https://pubmatic.bbvms.com/r/'.concat('$RENDERER', '.js'); // URL of the renderer application -const MSG_VIDEO_PLACEMENT_MISSING = 'Video.Placement param missing'; +const MSG_VIDEO_PLCMT_MISSING = 'Video.plcmt param missing'; const CUSTOM_PARAMS = { 'kadpageurl': '', // Custom page url @@ -74,6 +74,10 @@ const NATIVE_ASSET_IMAGE_TYPE = { 'IMAGE': 3 } +const BANNER_CUSTOM_PARAMS = { + 'battr': DATA_TYPES.ARRAY +} + const NET_REVENUE = true; const dealChannelValues = { 1: 'PMP', @@ -557,6 +561,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 +578,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 +681,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 +691,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); @@ -1098,7 +1113,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) => { @@ -1423,7 +1437,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..639a39d4636 100644 --- a/modules/pubwiseBidAdapter.js +++ b/modules/pubwiseBidAdapter.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 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/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/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.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/richaudienceBidAdapter.js b/modules/richaudienceBidAdapter.js old mode 100755 new mode 100644 index b63e31266fb..776b855dfba --- a/modules/richaudienceBidAdapter.js +++ b/modules/richaudienceBidAdapter.js @@ -37,7 +37,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 +44,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, @@ -57,10 +55,11 @@ export const spec = { cpuc: (typeof window.navigator != 'undefined' ? window.navigator.hardwareConcurrency : null), kws: getAllOrtbKeywords(bidderRequest.ortb2, bid.params.keywords).join(','), 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 = ''; @@ -154,7 +153,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 +375,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..4f8c8c1c550 100644 --- a/modules/rtbhouseBidAdapter.js +++ b/modules/rtbhouseBidAdapter.js @@ -114,11 +114,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 +166,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 +173,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 +220,7 @@ export const spec = { logInfo('Response with FLEDGE:', { bids, fledgeAuctionConfigs }); return { bids, - fledgeAuctionConfigs, + paapi: fledgeAuctionConfigs, } } return bids; @@ -250,7 +261,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 { 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..18736c6b0ec 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); diff --git a/modules/rubiconBidAdapter.js b/modules/rubiconBidAdapter.js index 9e47807bdc0..0a27a1116a4 100644 --- a/modules/rubiconBidAdapter.js +++ b/modules/rubiconBidAdapter.js @@ -22,6 +22,7 @@ import { _each } 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 @@ -219,9 +220,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); @@ -736,7 +737,7 @@ export const spec = { }); if (fledgeAuctionConfigs) { - return { bids, fledgeAuctionConfigs }; + return { bids, paapi: fledgeAuctionConfigs }; } else { return bids; } @@ -744,26 +745,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; 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..cffe47dde86 100644 --- a/modules/seedtagBidAdapter.js +++ b/modules/seedtagBidAdapter.js @@ -128,6 +128,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, @@ -293,7 +294,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 +343,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 { 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..4096d0320e9 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, 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') { 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..062e567a1c1 100644 --- a/modules/showheroes-bsBidAdapter.js +++ b/modules/showheroes-bsBidAdapter.js @@ -9,6 +9,12 @@ import { config } from '../src/config.js'; import { Renderer } from '../src/Renderer.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; import { VIDEO, BANNER } from '../src/mediaTypes.js'; +/** + * See https://github.com/prebid/Prebid.js/pull/4222 for details on linting exception + * ShowHeroes only imports after winning a bid + * Also see https://github.com/prebid/Prebid.js/issues/11656 + */ +// eslint-disable-next-line no-restricted-imports import { loadExternalScript } from '../src/adloader.js'; const PROD_ENDPOINT = 'https://bs.showheroes.com/api/v1/bid'; @@ -332,7 +338,7 @@ function createOutstreamEmbedCode(bid) { const fragment = window.document.createDocumentFragment(); - let script = loadExternalScript(urls.pubTag, 'outstream', function () { + let script = loadExternalScript(urls.pubTag, 'showheroes-bs', function () { window.ShowheroesTag = this; }); script.setAttribute('data-player-host', urls.vlHost); 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..1d5662f88eb 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 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..840ba8a82aa 100644 --- a/modules/smartadserverBidAdapter.js +++ b/modules/smartadserverBidAdapter.js @@ -1,4 +1,14 @@ -import { deepAccess, deepClone, isArrayOfNums, isFn, isInteger, isPlainObject, logError } from '../src/utils.js'; +import { + deepAccess, + deepClone, + isArray, + isArrayOfNums, + isEmpty, + isFn, + isInteger, + isPlainObject, + logError +} from '../src/utils.js'; import { BANNER, VIDEO } from '../src/mediaTypes.js'; import { config } from '../src/config.js'; import { registerBidder } from '../src/adapters/bidderFactory.js'; @@ -156,6 +166,9 @@ export const spec = { method: 'POST', url: (domain !== undefined ? domain : 'https://prg.smartadserver.com') + '/prebid/v1', data: JSON.stringify(payload), + options: { + browsingTopics: false + } }; }, @@ -196,11 +209,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; @@ -282,7 +300,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) { diff --git a/modules/smarthubBidAdapter.js b/modules/smarthubBidAdapter.js index 2889bd5358b..367011d68d5 100644 --- a/modules/smarthubBidAdapter.js +++ b/modules/smarthubBidAdapter.js @@ -1,190 +1,82 @@ -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: 'markapp', skipPbsAliasing: true}, + {code: 'jdpmedia', skipPbsAliasing: true}, + {code: 'tredio', skipPbsAliasing: true}, + {code: 'felixads', skipPbsAliasing: true}, + {code: 'vimayx', skipPbsAliasing: true}, +]; +const BASE_URLS = { + smarthub: 'https://prebid.smart-hub.io/pbjs', + markapp: 'https://markapp-prebid.smart-hub.io/pbjs', + jdpmedia: 'https://jdpmedia-prebid.smart-hub.io/pbjs', + tredio: 'https://tredio-prebid.smart-hub.io/pbjs', + felixads: 'https://felixads-prebid.smart-hub.io/pbjs', + vimayx: 'https://vimayx-prebid.smart-hub.io/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/smartxBidAdapter.js b/modules/smartxBidAdapter.js index 8394814365c..483a7a86d73 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 }, 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/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/snigelBidAdapter.js b/modules/snigelBidAdapter.js index 5a327b05cd0..4e0de53ca0d 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'; @@ -46,7 +45,7 @@ export const spec = { gdprConsent: gdprApplies === true ? hasFullGdprConsent(deepAccess(bidderRequest, 'gdprConsent')) : false, cur: getCurrencies(), test: getTestFlag(), - version: getGlobal().version, + version: 'v' + '$prebid.version$', gpp: deepAccess(bidderRequest, 'gppConsent.gppString') || deepAccess(bidderRequest, 'ortb2.regs.gpp'), gpp_sid: deepAccess(bidderRequest, 'gppConsent.applicableSections') || deepAccess(bidderRequest, 'ortb2.regs.gpp_sid'), 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..5478f9aa6e5 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 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..e05c9db6303 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 }); } } @@ -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..dc4cd13d4a1 --- /dev/null +++ b/modules/ssp_genieeBidAdapter.js @@ -0,0 +1,437 @@ +/* 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(); + 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 + ''; +} + +/** + * add imuid script tag + */ +function appendImuidScript() { + const scriptEl = document.createElement('script'); + scriptEl.src = '//dmp.im-apps.net/scripts/im-uid-hook.js?cid=3929'; + scriptEl.async = true; + document.body.appendChild(scriptEl); +} + +/** + * return imuid strings as query parameters + */ +function getImuidAsQueryParameter() { + const imuid = storage.getCookie('_im_uid.3929'); + 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; + } + + appendImuidScript(); + + 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..35f40953b1f 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, @@ -147,8 +146,6 @@ export const spec = { } }; -const isSecureWindow = () => getWindowSelf().location.protocol === 'https:'; - const isMainPageAccessible = () => { try { return !!getWindowTop().location.href; 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 new file mode 100644 index 00000000000..aeb42226470 --- /dev/null +++ b/modules/symitriDapRtdProvider.js @@ -0,0 +1,861 @@ +/** + * This module adds the Symitri DAP RTD provider to the real time data module + * The {@link module:modules/realTimeData} module is required + * The module will fetch real-time data from DAP + * @module modules/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 { loadExternalScript } from '../src/adloader.js'; +import {MODULE_TYPE_RTD} from '../src/activities/modules.js'; + +/** + * @typedef {import('../modules/rtdModule/index.js').RtdSubmodule} RtdSubmodule + */ +export function createRtdProvider(moduleName, moduleCode, headerPrefix) { + const MODULE_NAME = 'realTimeData'; + const SUBMODULE_NAME = moduleName; + const MODULE_CODE = moduleCode; + + const DAP_TOKEN = 'async_dap_token'; + const DAP_MEMBERSHIP = 'async_dap_membership'; + const DAP_ENCRYPTED_MEMBERSHIP = 'encrypted_dap_membership'; + 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 storage = getStorageManager({moduleType: MODULE_TYPE_RTD, moduleName: SUBMODULE_NAME}); + let dapRetryTokenize = 0; + + /** + * Lazy merge objects. + * @param {String} target + * @param {String} source + */ + function mergeLazy(target, source) { + if (!isPlainObject(target)) { + target = {}; + } + if (!isPlainObject(source)) { + source = {}; + } + return mergeDeep(target, source); + } + + /** + * Add real-time data & merge segments. + * @param {Object} ortb2 destination object to merge RTD into + * @param {Object} rtd + */ + function addRealTimeData(ortb2, rtd) { + logInfo('DEBUG(addRealTimeData) - ENTER'); + if (isPlainObject(rtd.ortb2)) { + logMessage('DEBUG(addRealTimeData): merging original: ', ortb2); + logMessage('DEBUG(addRealTimeData): merging in: ', rtd.ortb2); + mergeLazy(ortb2, rtd.ortb2); + } + logInfo('DEBUG(addRealTimeData) - EXIT'); + } + + /** + * Real-time data retrieval from Audigent + * @param {Object} bidConfig + * @param {function} onDone + * @param {Object} rtdConfig + * @param {Object} userConsent + */ + function getRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + 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)) { + 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 (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) }); + } else { + reject(Error('Please check if dapEntropyUrl is specified and is valid under config.params')); + } + } + } + }); + + loadScriptPromise + .catch((error) => { + logError('Entropy could not be calculated due to: ', error.message); + }) + .finally(() => { + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + }); + } else { + logMessage('No dapEntropyUrl is specified.'); + generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent); + } + } + + function generateRealTimeData(bidConfig, onDone, rtdConfig, userConsent) { + logInfo('DEBUG(generateRealTimeData) - ENTER'); + logMessage(' - apiHostname: ' + rtdConfig.params.apiHostname); + logMessage(' - apiVersion: ' + rtdConfig.params.apiVersion); + dapRetryTokenize = 0; + var jsonData = null; + if (rtdConfig && isPlainObject(rtdConfig.params)) { + if (rtdConfig.params.segtax == 504) { + let encMembership = dapUtils.dapGetEncryptedMembershipFromLocalStorage(); + if (encMembership) { + jsonData = dapUtils.dapGetEncryptedRtdObj(encMembership, rtdConfig.params.segtax) + } + } else { + let membership = dapUtils.dapGetMembershipFromLocalStorage(); + if (membership) { + jsonData = dapUtils.dapGetRtdObj(membership, rtdConfig.params.segtax) + } + } + } + if (jsonData) { + if (jsonData.rtd) { + addRealTimeData(bidConfig.ortb2Fragments?.global, jsonData.rtd); + onDone(); + logInfo('DEBUG(generateRealTimeData) - 1'); + // Don't return - ensure the data is always fresh. + } + } + // Calling setTimeout to release the main thread so that the bid request could be sent. + setTimeout(dapUtils.callDapAPIs, 0, bidConfig, onDone, rtdConfig, userConsent); + } + + /** + * Module init + * @param {Object} config + * @param {Object} userConsent + * @return {boolean} + */ + function init(config, userConsent) { + if (dapUtils.checkConsent(userConsent) === false) { + return false; + } + return true; + } + + function onBidResponse(bidResponse, config, userConsent) { + 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) => { + deal = JSON.parse(deal); + if (bidResponse.dealId == deal.id) { // Check if the bid response deal Id matches to the deals mapped to the user + let token = dapUtils.dapGetTokenFromLocalStorage(); + let url = config.params.pixelUrl + '?token=' + 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; + bidResponse.ad = `${bidResponse.ad} - - - `; + return bids; + } } 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..73ea5dae575 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 @@ -65,11 +66,11 @@ export const spec = { 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 +82,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.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..a665de6140f 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); } 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/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..39d77c81b57 100644 --- a/modules/unrulyBidAdapter.js +++ b/modules/unrulyBidAdapter.js @@ -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..3a6023f1f1c 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,12 +153,12 @@ 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 = { @@ -184,7 +171,7 @@ let addedUserIdHook = false; /** @type {SubmoduleContainer[]} */ let submodules = []; -/** @type {SubmoduleContainer[]} */ +/** @type {PriorityMaps} */ let initializedSubmodules; /** @type {SubmoduleConfig[]} */ @@ -239,6 +226,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 +258,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 getValueFromCookie(submodule, storedKey) { + return submodule.storageMgr.getCookie(storedKey) } -function setPrebidServerEidPermissions(initializedSubmodules) { - let setEidPermissions = getPrebidInternal().setEidPermissions; - if (typeof setEidPermissions === 'function' && isArray(initializedSubmodules)) { - setEidPermissions(buildEidPermissions(initializedSubmodules)); +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 +339,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 +369,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 +387,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 +406,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 +608,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,13 +681,12 @@ 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); @@ -609,7 +698,7 @@ export const requestBidsHook = timedAuctionHook('userId', function requestBidsHo * Simple use case will be passing these UserIds to A9 wrapper solution */ function getUserIds() { - return getCombinedSubmoduleIds(initializedSubmodules) + return getIds(initializedSubmodules.combined) } /** @@ -617,7 +706,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 +715,7 @@ function getUserIdsAsEids() { */ function getUserIdsAsEidBySource(sourceName) { - return createEidsArray(getSubmoduleId(initializedSubmodules, sourceName))[0]; + return getUserIdsAsEids().filter(eid => eid.source === sourceName)[0]; } /** @@ -634,7 +723,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 +782,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 +810,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 +831,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 +850,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 +869,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 +905,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 +925,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 +950,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 +958,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 +981,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 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; + } + + 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 +1099,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 +1109,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 (!addedUserIdHook) { + startAuction.before(startAuctionHook, 100) // use higher priority than dataController / rtd + adapterManager.callDataDeletionRequest.before(requestDataDeletion); + coreGetPPID.after((next) => next(getPPID())); + addedUserIdHook = true; + } 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 +1137,8 @@ function updateIdPriority(idPriorityConfig, submodules) { } else { idPriority = {}; } + initializedSubmodules.refresh(); + updateEIDConfig(submodules) } export function requestDataDeletion(next, ...args) { @@ -1031,6 +1161,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 +1197,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 +1212,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}); } } @@ -1105,11 +1235,3 @@ export function init(config, {delay = GreedyPromise.timeout} = {}) { 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..9fb53c2c7b3 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: { diff --git a/modules/utiqSystem.js b/modules/utiqIdSystem.js similarity index 90% rename from modules/utiqSystem.js rename to modules/utiqIdSystem.js index 473dc5854a9..d6b8aae44a3 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,6 @@ function getUtiqFromStorage() { let utiqPassStorage = JSON.parse( storage.getDataFromLocalStorage('utiqPass') ); - logInfo( - `${LOG_PREFIX}: Local storage utiqPass: ${JSON.stringify( - utiqPassStorage - )}` - ); if ( utiqPassStorage && @@ -40,12 +35,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 +58,7 @@ function getUtiqFromStorage() { } /** @type {Submodule} */ -export const utiqSubmodule = { +export const utiqIdSubmodule = { /** * Used to link submodule with config * @type {string} @@ -135,4 +137,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..f375e161f88 100644 --- a/modules/vdoaiBidAdapter.js +++ b/modules/vdoaiBidAdapter.js @@ -48,7 +48,6 @@ export const spec = { id: bidRequest.auctionId, mediaType: bidRequest.mediaTypes.video ? 'video' : 'banner' }; - bidRequest.params.bidFloor && (payload['bidFloor'] = bidRequest.params.bidFloor); return { method: 'POST', url: ENDPOINT_URL, 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..b62474d0c25 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', 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/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/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..94bb8c6440a --- /dev/null +++ b/modules/wurflRtdProvider.js @@ -0,0 +1,213 @@ +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'; + +// 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_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]); + logger.logMessage(`injecting data for ${bidderCode}: `, data); + enrichBidderRequest(reqBidsConfigObj, bidderCode, data); + return; + } + // inject WURFL low entropy data + const data = lowEntropyData(wjsResponse.WURFL, wjsResponse.wurfl_pbjs?.low_entropy_caps); + logger.logMessage(`injecting low entropy data for ${bidderCode}: `, data); + 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; + }); + 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': { + 'wurfl': wurflData, + } + }, + }; + mergeDeep(reqBidsConfigObj.ortb2Fragments.bidder, { [bidderCode]: ortb2data }); +} + +/** + * 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 98% rename from modules/yahoosspBidAdapter.js rename to modules/yahooAdsBidAdapter.js index 0453350a85a..eaffa1f6fae 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', @@ -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/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..67a34ae4870 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'], }, }; 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..02cfb8739b7 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), + page: args.find(t => t?.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..98551c7dd57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,44 +1,43 @@ { "name": "prebid.js", - "version": "8.49.8", + "version": "9.15.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "prebid.js", - "version": "8.49.8", + "version": "9.15.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": "^6.7.3" }, "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,11 @@ } }, "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "dependencies": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "picocolors": "^1.0.0" }, "engines": { @@ -143,28 +142,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.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==", "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.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "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.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -180,9 +179,9 @@ } }, "node_modules/@babel/eslint-parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz", - "integrity": "sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", "dev": true, "dependencies": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -198,11 +197,11 @@ } }, "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.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "dependencies": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" @@ -212,35 +211,36 @@ } }, "node_modules/@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "dependencies": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "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.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "dependencies": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" }, @@ -249,18 +249,18 @@ } }, "node_modules/@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "semver": "^6.3.1" }, "engines": { @@ -271,11 +271,11 @@ } }, "node_modules/@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" }, @@ -302,68 +302,72 @@ } }, "node_modules/@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "dependencies": { + "@babel/types": "^7.24.7" + }, "engines": { "node": ">=6.9.0" } }, "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" } }, "node_modules/@babel/helper-member-expression-to-functions": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "dependencies": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "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.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "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.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" }, "engines": { "node": ">=6.9.0" @@ -373,32 +377,32 @@ } }, "node_modules/@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -408,13 +412,13 @@ } }, "node_modules/@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -424,94 +428,97 @@ } }, "node_modules/@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "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==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "dependencies": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", + "license": "MIT", "dependencies": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "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.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==", "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.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==", "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.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==", "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", "dependencies": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" }, "engines": { "node": ">=6.9.0" } }, "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.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "dependencies": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "dependencies": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" @@ -521,9 +528,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.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "dependencies": { + "@babel/types": "^7.25.6" + }, "bin": { "parser": "bin/babel-parser.js" }, @@ -532,12 +542,12 @@ } }, "node_modules/@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", - "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -547,11 +557,11 @@ } }, "node_modules/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -561,13 +571,13 @@ } }, "node_modules/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -577,12 +587,12 @@ } }, "node_modules/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -597,6 +607,7 @@ "integrity": "sha512-nutsvktDItsNn4rpGItSNV2sz1XwS+nfU0Rg8aCx3W3NOKVzdMjJRu0O5OkgDp3ZGICSTbgRpxZoWsxoKRvbeA==", "deprecated": "This proposal has been merged to the ECMAScript standard and thus this plugin is no longer maintained. Please use @babel/plugin-transform-private-methods instead.", "dev": true, + "license": "MIT", "dependencies": { "@babel/helper-create-class-features-plugin": "^7.18.6", "@babel/helper-plugin-utils": "^7.18.6" @@ -678,11 +689,11 @@ } }, "node_modules/@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -692,11 +703,11 @@ } }, "node_modules/@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -837,11 +848,11 @@ } }, "node_modules/@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -851,13 +862,13 @@ } }, "node_modules/@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", "dependencies": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", "@babel/plugin-syntax-async-generators": "^7.8.4" }, "engines": { @@ -868,13 +879,13 @@ } }, "node_modules/@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "dependencies": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -884,11 +895,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -898,11 +909,11 @@ } }, "node_modules/@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -912,12 +923,12 @@ } }, "node_modules/@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -927,12 +938,12 @@ } }, "node_modules/@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" }, "engines": { @@ -943,17 +954,17 @@ } }, "node_modules/@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" }, "engines": { @@ -964,12 +975,12 @@ } }, "node_modules/@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -979,11 +990,11 @@ } }, "node_modules/@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -993,12 +1004,12 @@ } }, "node_modules/@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1008,11 +1019,11 @@ } }, "node_modules/@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1022,11 +1033,11 @@ } }, "node_modules/@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" }, "engines": { @@ -1037,12 +1048,12 @@ } }, "node_modules/@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "dependencies": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1052,11 +1063,11 @@ } }, "node_modules/@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" }, "engines": { @@ -1067,12 +1078,12 @@ } }, "node_modules/@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1082,13 +1093,13 @@ } }, "node_modules/@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1098,11 +1109,11 @@ } }, "node_modules/@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" }, "engines": { @@ -1113,11 +1124,11 @@ } }, "node_modules/@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1127,11 +1138,11 @@ } }, "node_modules/@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" }, "engines": { @@ -1142,11 +1153,11 @@ } }, "node_modules/@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1156,12 +1167,12 @@ } }, "node_modules/@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1171,13 +1182,13 @@ } }, "node_modules/@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1187,14 +1198,14 @@ } }, "node_modules/@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", "dependencies": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1204,12 +1215,12 @@ } }, "node_modules/@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "dependencies": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1219,12 +1230,12 @@ } }, "node_modules/@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1234,11 +1245,11 @@ } }, "node_modules/@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1248,11 +1259,11 @@ } }, "node_modules/@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" }, "engines": { @@ -1263,11 +1274,11 @@ } }, "node_modules/@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" }, "engines": { @@ -1278,14 +1289,14 @@ } }, "node_modules/@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "dependencies": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/plugin-transform-parameters": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1295,12 +1306,12 @@ } }, "node_modules/@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1310,11 +1321,11 @@ } }, "node_modules/@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" }, "engines": { @@ -1325,12 +1336,12 @@ } }, "node_modules/@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" }, "engines": { @@ -1341,11 +1352,11 @@ } }, "node_modules/@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1355,12 +1366,12 @@ } }, "node_modules/@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "dependencies": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1370,13 +1381,13 @@ } }, "node_modules/@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "dependencies": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" }, "engines": { @@ -1387,11 +1398,11 @@ } }, "node_modules/@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1401,11 +1412,11 @@ } }, "node_modules/@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" }, "engines": { @@ -1416,11 +1427,11 @@ } }, "node_modules/@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1430,12 +1441,12 @@ } }, "node_modules/@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "dependencies": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.1", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -1449,11 +1460,11 @@ } }, "node_modules/@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1463,12 +1474,12 @@ } }, "node_modules/@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1478,11 +1489,11 @@ } }, "node_modules/@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1492,11 +1503,11 @@ } }, "node_modules/@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1506,11 +1517,11 @@ } }, "node_modules/@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1520,11 +1531,11 @@ } }, "node_modules/@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "dependencies": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1534,12 +1545,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1549,12 +1560,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1564,12 +1575,12 @@ } }, "node_modules/@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", "dependencies": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" }, "engines": { "node": ">=6.9.0" @@ -1579,26 +1590,26 @@ } }, "node_modules/@babel/preset-env": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", - "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", - "dependencies": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "dependencies": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -1610,54 +1621,54 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.5", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.5", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.5", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.5", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.5", - "@babel/plugin-transform-parameters": "^7.24.5", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.5", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.5", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.4", @@ -1685,15 +1696,156 @@ "@babel/core": "^7.0.0-0 || ^8.0.0-0 <8.0.0" } }, + "node_modules/@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "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/register/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/@babel/register/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/@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "node_modules/@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "dependencies": { "regenerator-runtime": "^0.14.0" }, @@ -1702,31 +1854,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.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "dependencies": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" }, "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.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "dependencies": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "debug": "^4.3.1", "globals": "^11.1.0" }, @@ -1735,18 +1884,28 @@ } }, "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.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "dependencies": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" }, "engines": { "node": ">=6.9.0" } }, + "node_modules/@browserstack/ai-sdk-node": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.8.tgz", + "integrity": "sha512-34snogSnvskHxUZYOX61ga1/oTlyXwneRtd7Epu2bEdSsRR1rMm8xXhO6DVrLsHPwPHz+ljAlwVwhcE2uKysxw==", + "dev": true, + "dependencies": { + "axios": "^1.6.2", + "uuid": "9.0.1" + } + }, "node_modules/@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -1766,17 +1925,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 +2536,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 +2551,268 @@ "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": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.4.7.tgz", + "integrity": "sha512-5YwCySyV1UEgqzz34gNsC38eKxRBtlRDpJLlKcRtTjlYA/yDKuc1rfw+hjw+2WJxbAZtaDPsRl5Zk7J14SBoBw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/core": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.10.tgz", + "integrity": "sha512-TdESOKSVwf6+YWDz8GhS6nKscwzkIyakEzCLJ5Vh6O3Co2ClhCJ0A4MG909MUWfaWdpJm7DE45ii51/2Kat9tA==", + "dev": true, + "dependencies": { + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.1.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-spinners": "^2.9.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.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", + "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", + "dev": true, + "dependencies": { + "undici-types": "~6.19.2" + } + }, + "node_modules/@inquirer/core/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/@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.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + }, + "node_modules/@inquirer/editor": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.1.22.tgz", + "integrity": "sha512-K1QwTu7GCK+nKOVRBp5HY9jt3DXOfPGPr6WRDrPImkcJRelG9UTx2cAtK1liXmibRrzJlTWOwqgWT3k2XnS62w==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "external-editor": "^3.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/expand": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.1.22.tgz", + "integrity": "sha512-wTZOBkzH+ItPuZ3ZPa9lynBsdMp6kQ9zbjVPYEtSBG7UulGjg2kQiAnUjgyG4SlntpTce5bOmXAPvE4sguXjpA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.5.tgz", + "integrity": "sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.2.9.tgz", + "integrity": "sha512-7Z6N+uzkWM7+xsE+3rJdhdG/+mQgejOVqspoW+w0AbSZnL6nq5tGMEVASaYVWbkoSzecABWwmludO2evU3d31g==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/number": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.0.10.tgz", + "integrity": "sha512-kWTxRF8zHjQOn2TJs+XttLioBih6bdc5CcosXIzZsrTY383PXI35DuhIllZKu7CdXFi2rz2BWPN9l0dPsvrQOA==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/password": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.1.22.tgz", + "integrity": "sha512-5Fxt1L9vh3rAKqjYwqsjU4DZsEvY/2Gll+QkqR4yEpy6wvzLxdSgFhUcxfDAOtO4BEoTreWoznC0phagwLU5Kw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "dependencies": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/rawlist": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.2.4.tgz", + "integrity": "sha512-pb6w9pWrm7EfnYDgQObOurh2d2YH07+eDo3xQBsNAM2GRhliz6wFXGi1thKQ4bN6B0xDd6C3tBsjdr3obsCl3Q==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/search": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.0.7.tgz", + "integrity": "sha512-p1wpV+3gd1eST/o5N3yQpYEdFNCzSP0Klrl+5bfD3cTTz8BGG6nf4Z07aBW0xjlKIj1Rp0y3x/X4cZYi6TfcLw==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/select": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.4.7.tgz", + "integrity": "sha512-JH7XqPEkBpNWp3gPCqWqY8ECbyMoFcCZANlL6pV9hf59qK6dGmkOlx1ydyhY+KZ0c5X74+W6Mtp+nm2QX0/MAQ==", + "dev": true, + "dependencies": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/type": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.2.tgz", + "integrity": "sha512-w9qFkumYDCNyDZmNQjf/n6qQuvQ4dMC3BJesY4oF+yr0CxR5vxujflAVeIcS6U336uzi9GM0kAfZlLrZ9UTkpA==", + "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", @@ -2264,18 +3071,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", @@ -2305,9 +3100,9 @@ } }, "node_modules/@percy/sdk-utils": { - "version": "1.28.6", - "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.6.tgz", - "integrity": "sha512-cfpylC1bvp93EPQpwvYybdw5I4/fAABKR0hfCcyN7LF0FRgXE3/Onj7ebW41gypDilw2Q1nW0gUSwPj9MyoTOg==", + "version": "1.28.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.7.tgz", + "integrity": "sha512-LIhfHnkcS0fyIdo3gvKn7rwodZjbEtyLkgiDRSRulcBOatI2mhn2Bh269sXXiiFTyAW2BDQjyE3DWc4hkGbsbQ==", "dev": true, "engines": { "node": ">=14" @@ -2336,6 +3131,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 +3191,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 +3226,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 +3286,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 +3297,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", @@ -2542,16 +3337,6 @@ "@types/json-schema": "*" } }, - "node_modules/@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "dependencies": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "node_modules/@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -2576,16 +3361,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 +3370,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 +3406,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 +3415,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,10 +3427,19 @@ "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", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, "dependencies": { "undici-types": "~5.26.4" @@ -2694,14 +3457,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 +3481,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 +3503,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.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "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,10 +3543,23 @@ "@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", - "integrity": "sha512-etPTUdCFu7gUWc+1XcbiPr+lrhOcBu3rV5OL1M+3PDW89zskScAkkcdqYzP4pFodBPye/ydamQoTDScOnElw5A==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.3.tgz", + "integrity": "sha512-91CJv5PnFBzNBvyEjt+9cPzTK/xoVixARj2g7ZAvItA+5bx8VKdk5RxCz/PP2kdzz9W+NiDUMPkdmTsosmy69Q==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", @@ -2832,6 +3605,18 @@ "is-function": "^1.0.1" } }, + "node_modules/@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "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", @@ -2908,119 +3693,137 @@ "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.0.5", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.0.5.tgz", + "integrity": "sha512-pJNb9jJwPf+FEwAEnnUc6d9s6/QlvcZbh9NtjO23a/wr3HvXdzhlRHwzUV1RWboDpGsww5PFmtGcIo7GdDQL+g==", "dev": true, "dependencies": { + "@browserstack/ai-sdk-node": "1.5.8", "@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.0.4", + "@wdio/reporter": "9.0.4", + "@wdio/types": "9.0.4", "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.0.5", "winston-transport": "^4.5.0", "yauzl": "^3.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" + "@wdio/cli": "^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 || ^9.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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "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" + "debug": "^4.3.5", + "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.3.0" + "node": ">=18" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "engines": { + "node": ">=18" + } + }, + "node_modules/@wdio/browserstack-service/node_modules/@wdio/reporter": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.0.4.tgz", + "integrity": "sha512-g55MiqToKEZ+L5quk7Ddk3m1fKgh2U2rq3zLG8bZUYU+3KFglfRC/Ld5yTso8S1tG4EDgl5HKSN5Bl8I5gncbg==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=18" } }, - "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/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "@types/node": "^20.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "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/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "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" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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": ">= 14" + "node": ">=18" } }, - "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/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": { - "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" + "debug": "^4.3.4" }, "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", @@ -3030,39 +3833,6 @@ "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_modules/@wdio/browserstack-service/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -3075,301 +3845,27 @@ "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==", - "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_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==", - "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" - }, - "engines": { - "node": "^16.13 || >=18" - } - }, - "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/browserstack-service/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "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_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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "4" - }, - "engines": { - "node": ">= 6.0.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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.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" } }, - "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==", + "node_modules/@wdio/browserstack-service/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", - "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" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "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==", - "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_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==", - "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_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==", - "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" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "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==", + "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,41 +3875,6 @@ "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==", - "dev": true, - "engines": { - "node": ">=8" - }, - "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": { "version": "7.18.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", @@ -3424,9 +3885,9 @@ } }, "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==", + "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" @@ -3438,209 +3899,135 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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==", - "dev": true, - "optional": true, - "peer": true - }, - "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==", - "dev": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, "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==", + "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==", - "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/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==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "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/browserstack-service/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": { - "type-fest": "^2.12.2" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "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/browserstack-service/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/browserstack-service/node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "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" - } + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "optional": true, - "peer": true, - "engines": { - "node": "*" + "bin": { + "uuid": "dist/bin/uuid" } }, "node_modules/@wdio/browserstack-service/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, - "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", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "devtools": "^8.14.0" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "devtools": { + "puppeteer-core": { "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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -3658,9 +4045,9 @@ } }, "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==", + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", "dev": true, "dependencies": { "cliui": "^8.0.1", @@ -3675,171 +4062,130 @@ "node": ">=12" } }, - "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==", - "dev": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/@wdio/cli": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-8.36.1.tgz", - "integrity": "sha512-LZBZiwcvvv5P0HuRXt8IV09UiFT5dnDr1Ag5u2roJL2D7l8wDHHa70PXw9MmlbrnyFCUN3hO7FQVUi9MAsDbDQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.0.5.tgz", + "integrity": "sha512-D/QBlodNIdxuNpUPbuhk+mLidVLT+Vsb0Q0Fd4lh57Jy8kw5nJ56ykqiI0WE1oI0i+XtyJ7iFOPUztuCjjhX3A==", "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", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", - "cli-spinners": "^2.9.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": "^10.1.8", "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", + "tsx": "^4.7.2", + "webdriverio": "9.0.5", "yargs": "^17.7.2" }, "bin": { "wdio": "bin/wdio.js" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", "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" + "debug": "^4.3.6", + "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.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "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/cli/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "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" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" + "node": ">=18" } }, - "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==", + "node_modules/@wdio/cli/node_modules/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "dependencies": { - "debug": "^4.3.4" + "@types/node": "^20.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "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/cli/node_modules/@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "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" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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": ">= 14" + "node": ">=18" } }, - "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/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, "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" + "debug": "^4.3.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==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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/cli/node_modules/brace-expansion": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", @@ -3849,39 +4195,6 @@ "balanced-match": "^1.0.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==", - "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/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==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/@wdio/cli/node_modules/chalk": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", @@ -3894,302 +4207,342 @@ "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/cli/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "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": ">=16.0.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/cli/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "dev": true, + "optional": true, + "peer": true + }, + "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, "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" + "@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": ">= 14" + "node": "^18.19.0 || >=20.5.0" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "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/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, + "dependencies": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { "node": ">= 14" } }, - "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/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, - "dependencies": { - "node-fetch": "^2.6.12" + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/cli/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/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, - "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" - }, "engines": { - "node": "^16.13 || >=18" + "node": ">=12" } }, - "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/cli/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, - "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" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "node": ">=16 || 14 >=14.17" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/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, - "optional": true, - "peer": true, "dependencies": { - "debug": "4" + "path-key": "^4.0.0", + "unicorn-magic": "^0.3.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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, - "dependencies": { - "mitt": "3.0.0" + "engines": { + "node": ">=18" }, - "peerDependencies": { - "devtools-protocol": "*" + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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, - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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==", + "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, - "optional": true, - "peer": true + "dependencies": { + "parse-ms": "^4.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "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/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, - "optional": true, - "peer": true, "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "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" }, "engines": { - "node": ">= 6" + "node": ">= 14" } }, - "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/cli/node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, "peer": true, "dependencies": { - "agent-base": "6", - "debug": "4" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">= 6" + "node": ">=18" } }, - "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/cli/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "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.5", + "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/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, - "optional": true, - "peer": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">= 6" + "node": ">=10" } }, - "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/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, - "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": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "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/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, - "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" + "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/webdriverio": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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.2.1", + "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.0.5" }, "engines": { - "node": ">=6" + "node": ">=18" + }, + "peerDependencies": { + "puppeteer-core": "^22.3.0" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } } }, - "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/cli/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "optional": true, "peer": 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/@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, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -4203,104 +4556,171 @@ "node": ">=12" } }, - "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/concise-reporter": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.38.2.tgz", + "integrity": "sha512-wE36By4Z9iCtRzihpYrmCehsmNc8t3gHviBsUxV4tmYh/SQr+WX/dysWnojer6KWIJ2rT0rOzyQPmrwhdFKAFg==", "dev": true, - "optional": true, - "peer": true, + "dependencies": { + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", + "chalk": "^5.0.1", + "pretty-ms": "^7.0.1" + }, "engines": { - "node": ">=10" + "node": "^16.13 || >=18" + } + }, + "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": "^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/config": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.5.tgz", + "integrity": "sha512-+dxUU2SLXLkqQhVU/wauU1VgqEKIFubOyUb6B0ueAMpM1aolc62zhE9D9rrQYbjkPOM7nFsjuuGR5+9+zaoZ6g==", "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" + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=16.17" + "node": ">=18" + } + }, + "node_modules/@wdio/config/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "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" }, - "funding": { - "url": "https://github.com/sindresorhus/execa?sponsor=1" + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" } }, - "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==", + "node_modules/@wdio/config/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "dev": true, + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, "engines": { - "node": ">=16" + "node": ">=18" + } + }, + "node_modules/@wdio/config/node_modules/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18" } }, - "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==", + "node_modules/@wdio/config/node_modules/@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "dependencies": { - "agent-base": "^7.0.2", - "debug": "4" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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": ">=18" + } + }, + "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==", + "dev": true, + "dependencies": { + "debug": "^4.3.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==", + "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, "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + "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/lighthouse-logger": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/lighthouse-logger/-/lighthouse-logger-2.0.1.tgz", - "integrity": "sha512-ioBrW3s2i97noEmnXxmUq7cjIcVRjT5HBpAYy8zE11CxU9HqlWHHeRxfeN1tn8F7OEMVPIC9x1f8t3Z7US9ehQ==", + "node_modules/@wdio/config/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "engines": { + "node": ">=16.0.0" } }, - "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==", + "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, - "optional": true, - "peer": true, "dependencies": { - "ms": "2.0.0" + "agent-base": "^7.0.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" } }, - "node_modules/@wdio/cli/node_modules/lru-cache": { + "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==", @@ -4309,290 +4729,420 @@ "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==", + "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, - "engines": { - "node": ">=12" + "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/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "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, - "dependencies": { - "brace-expansion": "^2.0.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=10" } }, - "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==", + "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, - "optional": true, - "peer": true + "dependencies": { + "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/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/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, "dependencies": { - "whatwg-url": "^5.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": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "node": ">=12" + } + }, + "node_modules/@wdio/globals": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.0.5.tgz", + "integrity": "sha512-ZkopKj1qEDNKuF1a87JTLfTKCBFgCHLUns5ob5D1oEmMFp0NwB89HHGBWgtuJpCUmxJAbf4rCKglVeKhB9rY7A==", + "dev": true, + "engines": { + "node": ">=18" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "optionalDependencies": { + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.0.5" } }, - "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==", + "node_modules/@wdio/globals/node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, + "optional": true, "dependencies": { - "path-key": "^4.0.0" + "debug": "^4.3.5", + "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": "^12.20.0 || ^14.13.1 || >=16.0.0" + "node": ">=18" + } + }, + "node_modules/@wdio/globals/node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", + "dev": true, + "optional": true, + "dependencies": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://opencollective.com/vitest" } }, - "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/globals/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "dev": true, + "optional": true, "dependencies": { - "mimic-fn": "^4.0.0" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18" } }, - "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/globals/node_modules/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, + "optional": true, + "dependencies": { + "@types/node": "^20.1.0" + }, "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@wdio/globals/node_modules/@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "optional": true, + "dependencies": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18" } }, - "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/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==", "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" + "debug": "^4.3.4" }, "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==", + "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==", "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" + "balanced-match": "^1.0.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, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@wdio/globals/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true, + "optional": true, "engines": { - "node": ">=16.3.0" + "node": ">=16.0.0" + } + }, + "node_modules/@wdio/globals/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", + "dev": true, + "optional": true, + "peer": true + }, + "node_modules/@wdio/globals/node_modules/expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", + "dev": true, + "optional": true, + "dependencies": { + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" + }, + "engines": { + "node": ">=18 || >=20 || >=22" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "@wdio/globals": "^9.0.0-alpha.350", + "@wdio/logger": "^9.0.0-alpha.350", + "webdriverio": "^9.0.0-alpha.350" }, "peerDependenciesMeta": { - "typescript": { - "optional": true + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false } } }, - "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==", + "node_modules/@wdio/globals/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, + "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" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 14" } }, - "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==", + "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, - "dependencies": { - "type-fest": "^2.12.2" - }, + "optional": true, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=12" } }, - "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==", + "node_modules/@wdio/globals/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, + "optional": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, "engines": { - "node": ">=14" + "node": ">=16 || 14 >=14.17" }, "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==", + "node_modules/@wdio/globals/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, + "optional": true, "dependencies": { - "safe-buffer": "~5.2.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": ">= 14" } }, - "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/globals/node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, - "engines": { - "node": ">=12.20" + "optional": true, + "peer": true, + "dependencies": { + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=18" } }, - "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==", + "node_modules/@wdio/globals/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, - "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, + "bin": { + "semver": "bin/semver.js" + }, "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/globals/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, + "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", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" + }, + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" + } + }, + "node_modules/@wdio/globals/node_modules/webdriverio": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "optional": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "devtools": "^8.14.0" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "devtools": { + "puppeteer-core": { "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==", + "node_modules/@wdio/globals/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -4609,11 +5159,12 @@ } } }, - "node_modules/@wdio/cli/node_modules/yargs": { + "node_modules/@wdio/globals/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, + "optional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -4627,36 +5178,53 @@ "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==", + "node_modules/@wdio/local-runner": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.0.5.tgz", + "integrity": "sha512-BFZ/e7z1s2cYsix1evijydaDn0YffeIHjPsMoa9b+zhW8BoZfTEDGKblYvzRgjUDD4elXs+YRZpA6EhjcGJTxQ==", "dev": true, "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" + "@types/node": "^20.1.0", + "@wdio/logger": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/runner": "9.0.5", + "@wdio/types": "9.0.4", + "async-exit-hook": "^2.0.1", + "split2": "^4.1.0", + "stream-buffers": "^3.0.2" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "node_modules/@wdio/concise-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==", + "node_modules/@wdio/local-runner/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "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", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, - "node_modules/@wdio/concise-reporter/node_modules/chalk": { + "node_modules/@wdio/local-runner/node_modules/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", + "dev": true, + "dependencies": { + "@types/node": "^20.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "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==", @@ -4668,471 +5236,279 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/@wdio/config": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.36.1.tgz", - "integrity": "sha512-yCENnym0CrYuLKMJ3fv00WkjCR8QpPqVohGBkq5FvZOZpVJEpoG86Q8l4HtyRnd6ggMTKCA1vTQ/myhbPmZmaQ==", + "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": { - "@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" + "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/globals": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-8.36.1.tgz", - "integrity": "sha512-Qpj6gZCRNxqdVkTwYyi4JdeYO4tLSUj3Ti6yxO0v9A4IRaKW1tS29KUcGgjL9CFSBKAOi2zRY8vvFz1u6ewxtQ==", + "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, "engines": { - "node": "^16.13 || >=18" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, - "optionalDependencies": { - "expect-webdriverio": "^4.11.2", - "webdriverio": "8.36.1" + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=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/mocha-framework": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.38.2.tgz", + "integrity": "sha512-qJmRL5E6/ypjCUACH4hvCAAmTdU4YUrUlp9o/IKvTIAHMnZPE0/HgUFixCeu8Mop+rdzTPVBrbqxpRDdSnraYA==", "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" + "@types/mocha": "^10.0.0", + "@types/node": "^20.1.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", + "mocha": "^10.0.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/protocols": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.4.tgz", + "integrity": "sha512-T9v8Jkp94NxLLil5J7uJ/+YHk5/7fhOggzGcN+LvuCHS6jbJFZ/11c4SUEuXw27Yqk01fFXPBbF6TwcTTOuW/Q==", + "dev": true + }, + "node_modules/@wdio/repl": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.4.tgz", + "integrity": "sha512-5Bc5ARjWA7t6MZNnVJ9WvO1iDsy6iOsrSDWiP7APWAdaF/SJCP3SFE2c+PdQJpJWhr2Kk0fRGuyDM+GdsmZhwg==", "dev": true, - "optional": true, "dependencies": { - "debug": "^4.3.4" + "@types/node": "^20.1.0" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "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/@wdio/reporter": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.38.2.tgz", + "integrity": "sha512-R78UdAtAnkaV22NYlCCcbPPhmYweiDURiw64LYhlVIQrKNuXUQcafR2kRlWKy31rZc9thSLs5LzrZDReENUlFQ==", "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" + "@types/node": "^20.1.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "diff": "^5.0.0", + "object-inspect": "^1.12.0" }, "engines": { - "node": ">= 14" + "node": "^16.13 || >=18" } }, - "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/@wdio/runner": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.0.5.tgz", + "integrity": "sha512-qZF7k3BeQaM7pQRwIvedbfaC7xBU1xRY+wFkp44U/wvYZOOrqWiwv/Synk1iCFkOdxl/b+Gqp68dDmS9BrVDmw==", "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" + "@types/node": "^20.11.28", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", + "gaze": "^1.1.3", + "webdriver": "9.0.5", + "webdriverio": "9.0.5" }, "engines": { - "node": ">= 14" + "node": ">=18" } }, - "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==", + "node_modules/@wdio/runner/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", "dev": true, - "optional": true + "dependencies": { + "debug": "^4.3.6", + "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": ">=18" + } }, - "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/@wdio/runner/node_modules/@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, - "optional": true, "dependencies": { - "balanced-match": "^1.0.0" + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + }, + "funding": { + "url": "https://opencollective.com/vitest" } }, - "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/@wdio/runner/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "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" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" } }, - "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/@wdio/runner/node_modules/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, - "optional": true, + "dependencies": { + "@types/node": "^20.1.0" + }, "engines": { - "node": ">=8.0.0" + "node": ">=18" } }, - "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/@wdio/runner/node_modules/@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "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" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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.13.0" + "node": ">=18" } }, - "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/@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, "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" + "debug": "^4.3.4" }, "engines": { "node": ">= 14" } }, - "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==", - "dev": true, - "optional": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "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==", - "dev": true, - "optional": true, - "dependencies": { - "node-fetch": "^2.6.12" - } - }, - "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/@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, - "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" - }, - "engines": { - "node": "^16.13 || >=18" + "balanced-match": "^1.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==", - "dev": true, - "optional": true - }, - "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/@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, - "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" + "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/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==", + "node_modules/@wdio/runner/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "4" - }, "engines": { - "node": ">= 6.0.0" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "mitt": "3.0.0" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "node-fetch": "^2.6.11" + "node": ">=16.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/@wdio/runner/node_modules/devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@wdio/globals/node_modules/devtools/node_modules/https-proxy-agent": { + "node_modules/@wdio/runner/node_modules/expect-webdriverio": { "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, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, - "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==", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "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" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" }, "engines": { - "node": ">=16.0.0" + "node": ">=18 || >=20 || >=22" }, "peerDependencies": { - "typescript": ">= 4.7.4" + "@wdio/globals": "^9.0.0-alpha.350", + "@wdio/logger": "^9.0.0-alpha.350", + "webdriverio": "^9.0.0-alpha.350" }, "peerDependenciesMeta": { - "typescript": { - "optional": true + "@wdio/globals": { + "optional": false + }, + "@wdio/logger": { + "optional": false + }, + "webdriverio": { + "optional": false } } }, - "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==", - "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_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==", - "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_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==", - "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" - }, - "engines": { - "node": ">=6" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "engines": { - "node": ">=10" - }, - "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/@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, - "optional": true, "dependencies": { "agent-base": "^7.0.2", "debug": "4" @@ -5141,58 +5517,20 @@ "node": ">= 14" } }, - "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==", - "dev": true, - "optional": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" - } - }, - "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==", - "dev": true, - "optional": true, - "peer": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/@wdio/globals/node_modules/lru-cache": { + "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, - "optional": true, "engines": { "node": ">=12" } }, - "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/@wdio/runner/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, - "optional": true, "dependencies": { "brace-expansion": "^2.0.1" }, @@ -5203,219 +5541,146 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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==", - "dev": true, - "optional": true, - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } - }, - "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/@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, - "optional": 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/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/@wdio/runner/node_modules/puppeteer-core": { + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, + "peer": 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" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.0" }, "engines": { - "node": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=18" } }, - "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==", - "dev": true, - "optional": true - }, - "node_modules/@wdio/globals/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/runner/node_modules/puppeteer-core/node_modules/@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, "optional": true, + "peer": true, "dependencies": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "debug": "^4.3.5", + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">=18" } }, - "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==", + "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, - "optional": true, - "dependencies": { - "type-fest": "^2.12.2" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=14.16" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=10" } }, - "node_modules/@wdio/globals/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/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, - "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" + "pump": "^3.0.0", + "tar-stream": "^3.1.5" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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==", - "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": "*" + "optionalDependencies": { + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0" } }, - "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", + "node_modules/@wdio/runner/node_modules/webdriverio": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.5" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" }, "peerDependencies": { - "devtools": "^8.14.0" + "puppeteer-core": "^22.3.0" }, "peerDependenciesMeta": { - "devtools": { + "puppeteer-core": { "optional": true } } }, - "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/@wdio/runner/node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "optional": true, + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -5432,12 +5697,11 @@ } } }, - "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/@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==", "dev": true, - "optional": true, "dependencies": { "cliui": "^8.0.1", "escalade": "^3.1.1", @@ -5451,56 +5715,23 @@ "node": ">=12" } }, - "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==", - "dev": true, - "optional": true, - "dependencies": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" - } - }, - "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==", - "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_modules/@wdio/logger": { - "version": "8.28.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-8.28.0.tgz", - "integrity": "sha512-/s6zNCqwy1hoc+K4SJypis0Ud0dlJ+urOelJFO1x0G0rwDRWyFiUP6ijTaCcFxAm29jYEcEPWijl2xkVIHwOyA==", + "node_modules/@wdio/spec-reporter": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.38.2.tgz", + "integrity": "sha512-Dntk+lmrp+0I3HRRWkkXED+smshvgsW5cdLKwJhEJ1liI48MdBpdNGf9IHTVckE6nfxcWDyFI4icD9qYv/5bFA==", "dev": true, "dependencies": { + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.1.2", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^7.1.0" + "easy-table": "^1.2.0", + "pretty-ms": "^7.0.0" }, "engines": { "node": "^16.13 || >=18" } }, - "node_modules/@wdio/logger/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==", @@ -5512,513 +5743,368 @@ "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==", + "node_modules/@wdio/types": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.38.2.tgz", + "integrity": "sha512-+wj1c1OSLdnN4WO5b44Ih4263dTl/eSwMGSI4/pCgIyXIuYQH38JQ+6WRa+c8vJEskUzboq2cSgEQumVZ39ozQ==", "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" + "@types/node": "^20.1.0" }, "engines": { "node": "^16.13 || >=18" } }, - "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/@wdio/utils": { + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.38.2.tgz", + "integrity": "sha512-y5AnBwsGcu/XuCBGCgKmlvKdwEIFyzLA+Cr+denySxY3jbWDONtPUcGaVdFALwsIa5jcIjcATqGmZcCPGnkd7g==", "dev": true, "dependencies": { - "@types/node": "^20.1.0" + "@puppeteer/browsers": "^1.6.0", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "decamelize": "^6.0.0", + "deepmerge-ts": "^5.1.0", + "edgedriver": "^5.5.0", + "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" } }, - "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/@webassemblyjs/ast": { + "version": "1.12.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.12.1.tgz", + "integrity": "sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg==", "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" + "@webassemblyjs/helper-numbers": "1.11.6", + "@webassemblyjs/helper-wasm-bytecode": "1.11.6" } }, - "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/@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": { - "@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" - }, - "engines": { - "node": "^16.13 || >=18" + "@webassemblyjs/floating-point-hex-parser": "1.11.6", + "@webassemblyjs/helper-api-error": "1.11.6", + "@xtuc/long": "4.2.2" } }, - "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/@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": { - "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 - } + "@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/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/@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": { - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" + "@xtuc/ieee754": "^1.2.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==", - "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_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/@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": { - "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" + "@xtuc/long": "4.2.2" } }, - "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==", + "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/@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==", + "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": { - "balanced-match": "^1.0.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/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==", + "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, - "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" + "@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/runner/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/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, - "engines": { - "node": ">=8.0.0" + "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/@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/@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, - "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-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/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/@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": { - "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", + "@xtuc/long": "4.2.2" } }, - "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/@xmldom/xmldom": { + "version": "0.8.10", + "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", + "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", "dev": true, - "dependencies": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" - }, "engines": { - "node": ">= 14" + "node": ">=10.0.0" } }, - "node_modules/@wdio/runner/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_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/@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/@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, - "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" - }, "engines": { - "node": "^16.13 || >=18" + "bun": ">=0.7.0", + "deno": ">=1.0.0", + "node": ">=16.5.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==", + "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/@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/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/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==", - "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/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/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/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/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/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==", - "dev": true, - "optional": true, - "peer": true - }, - "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/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, - "dependencies": { - "@tootallnate/once": "2", - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "node": ">=0.4.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/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": { - "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/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/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": { - "@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" + "debug": "4" }, "engines": { - "node": ">=16.0.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 6.0.0" } }, - "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/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": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">= 6" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "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==", + "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, - "optional": true, - "peer": true, - "dependencies": { - "chownr": "^1.1.1", - "mkdirp-classic": "^0.5.2", - "pump": "^3.0.0", - "tar-stream": "^2.1.4" + "peerDependencies": { + "ajv": "^6.9.1" } }, - "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/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": { - "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": ">=0.4.2" } }, - "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==", + "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, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=6" } }, - "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/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": { - "agent-base": "^7.0.2", - "debug": "4" + "ansi-wrap": "0.1.0" }, "engines": { - "node": ">= 14" + "node": ">=0.10.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/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" }, @@ -6026,163 +6112,172 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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, - "peer": true, "dependencies": { - "debug": "^2.6.9", - "marky": "^1.2.2" + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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/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, - "peer": true, "dependencies": { - "ms": "2.0.0" + "ansi-wrap": "0.1.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": ">=12" + "node": ">=8" } }, - "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==", - "dev": true, + "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": { - "brace-expansion": "^2.0.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/@wdio/runner/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/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/@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/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": { - "whatwg-url": "^5.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">= 8" } }, - "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/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": { - "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" + "buffer-equal": "^1.0.0" }, "engines": { - "node": ">= 14" + "node": ">=0.10.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/archiver": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", + "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", "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" + "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": ">=16.3.0" - }, - "peerDependencies": { - "typescript": ">= 4.7.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">= 14" } }, - "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==", - "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/archiver-utils": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", + "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", "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" + "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": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": ">= 14" } }, - "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/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": { - "type-fest": "^2.12.2" - }, + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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, "engines": { - "node": ">=14.16" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@wdio/runner/node_modules/string_decoder": { + "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, + "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/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==", @@ -6191,931 +6286,322 @@ "safe-buffer": "~5.2.0" } }, - "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==", - "dev": 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/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/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, + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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, "engines": { - "node": "*" + "node": ">=8.0.0" } }, - "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/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, "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" + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" }, "engines": { - "node": "^16.13 || >=18" - }, - "peerDependencies": { - "devtools": "^8.14.0" - }, - "peerDependenciesMeta": { - "devtools": { - "optional": true - } + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "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/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, - "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 - } + "dependencies": { + "safe-buffer": "~5.2.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==", + "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": { - "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": ">=14" } }, - "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==", + "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": { - "archiver-utils": "^5.0.0", - "compress-commons": "^6.0.2", - "readable-stream": "^4.0.0" - }, - "engines": { - "node": ">= 14" + "dequal": "^2.0.3" } }, - "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/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": { - "@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-flatten": "^1.0.1", + "array-slice": "^0.2.3" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "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/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": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=0.10.0" } }, - "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/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.1.0" + "make-iterator": "^1.0.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=0.10.0" } }, - "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/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": { - "@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" + "node": ">=0.10.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/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": { - "@webassemblyjs/helper-numbers": "1.11.6", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6" + "make-iterator": "^1.0.0" + }, + "engines": { + "node": ">=0.10.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==", + "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": { - "@webassemblyjs/floating-point-hex-parser": "1.11.6", - "@webassemblyjs/helper-api-error": "1.11.6", - "@xtuc/long": "4.2.2" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/helper-wasm-bytecode": "1.11.6", - "@webassemblyjs/wasm-gen": "1.12.1" + "call-bind": "^1.0.5", + "is-array-buffer": "^3.0.4" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/@webassemblyjs/ieee754": { - "version": "1.11.6", - "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz", - "integrity": "sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg==", + "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, - "dependencies": { - "@xtuc/ieee754": "^1.2.0" + "engines": { + "node": ">=0.10.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==", + "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, - "dependencies": { - "@xtuc/long": "4.2.2" + "engines": { + "node": ">=0.10.0" } }, - "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/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/@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/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/@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==", + "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": { - "@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" + "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/@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==", + "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": { - "@webassemblyjs/ast": "1.12.1", - "@webassemblyjs/helper-buffer": "1.12.1", - "@webassemblyjs/wasm-gen": "1.12.1", - "@webassemblyjs/wasm-parser": "1.12.1" + "array-slice": "^1.0.0", + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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, - "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" + "engines": { + "node": ">=0.10.0" } }, - "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==", + "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": { - "@webassemblyjs/ast": "1.12.1", - "@xtuc/long": "4.2.2" + "is-number": "^4.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/@xmldom/xmldom": { - "version": "0.8.10", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", - "integrity": "sha512-2WALfTl4xo2SkGCYRt6rDTFfk9R1czmBvUQy12gK2KuRKIpWEhcbbzy8EZXtz/jkRqHX8bFEc6FC1HjX4TUWYw==", + "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": ">=10.0.0" + "node": ">=0.10.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==", + "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, - "dependencies": { - "event-target-shim": "^5.0.0" - }, "engines": { - "node": ">=6.5" + "node": ">=0.10.0" } }, - "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==", + "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": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "default-compare": "^1.0.0", + "get-value": "^2.0.6", + "kind-of": "^5.0.2" }, "engines": { - "node": ">= 0.6" + "node": ">=0.10.0" } }, - "node_modules/acorn": { - "version": "7.4.1", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", - "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "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, - "bin": { - "acorn": "bin/acorn" - }, "engines": { - "node": ">=0.4.0" + "node": ">=0.10.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==", + "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, - "peerDependencies": { - "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + "engines": { + "node": ">=0.10.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==", + "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.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": ">=0.10.0" } }, "node_modules/array.prototype.findlastindex": { @@ -7257,9 +6743,9 @@ } }, "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==", + "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": { @@ -7368,180 +6854,41 @@ } }, "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==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "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==", + "node_modules/axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dev": true, "dependencies": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" } }, - "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==", + "node_modules/axios/node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "dev": true, + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, "engines": { - "node": ">=0.10.0" + "node": ">= 6" } }, - "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/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", @@ -7562,15 +6909,6 @@ "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", @@ -7623,137 +6961,6 @@ "@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", @@ -7791,22 +6998,22 @@ "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==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", "dev": true, "optional": true, "dependencies": { "bare-events": "^2.0.0", "bare-path": "^2.0.0", - "bare-stream": "^1.0.0" + "bare-stream": "^2.0.0" } }, "node_modules/bare-os": { @@ -7817,9 +7024,9 @@ "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==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", "dev": true, "optional": true, "dependencies": { @@ -7827,13 +7034,13 @@ } }, "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==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", "dev": true, "optional": true, "dependencies": { - "streamx": "^2.16.1" + "streamx": "^2.18.0" } }, "node_modules/base": { @@ -8011,31 +7218,6 @@ "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", @@ -8054,9 +7236,9 @@ } }, "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==", + "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": { "bytes": "3.1.2", "content-type": "~1.0.5", @@ -8066,7 +7248,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" @@ -8131,12 +7313,12 @@ } }, "node_modules/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, "dependencies": { - "fill-range": "^7.0.1" + "fill-range": "^7.1.1" }, "engines": { "node": ">=8" @@ -8149,9 +7331,9 @@ "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==", + "version": "4.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "funding": [ { "type": "opencollective", @@ -8167,10 +7349,10 @@ } ], "dependencies": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" }, "bin": { "browserslist": "cli.js" @@ -8443,45 +7625,6 @@ "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", @@ -8525,9 +7668,9 @@ "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==", + "version": "1.0.30001633", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz", + "integrity": "sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==", "funding": [ { "type": "opencollective", @@ -8650,6 +7793,60 @@ "node": "*" } }, + "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": { + "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": ">=18.17" + }, + "funding": { + "url": "https://github.com/cheeriojs/cheerio?sponsor=1" + } + }, + "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, + "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/fb55" + } + }, + "node_modules/cheerio/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/chokidar": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", @@ -8674,58 +7871,26 @@ "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==", + "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": ">=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==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, + "optional": true, + "peer": true, "dependencies": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" }, "peerDependencies": { "devtools-protocol": "*" @@ -8808,12 +7973,12 @@ } }, "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==", + "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": ">=6" + "node": ">=18.20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -8922,23 +8087,37 @@ "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==", + "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": ">=6" + } + }, + "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": { - "mimic-response": "^1.0.0" + "isobject": "^3.0.1" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "engines": { + "node": ">=0.10.0" } }, "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, + "extraneous": true, "engines": { "node": ">=4" } @@ -9056,9 +8235,9 @@ "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==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/comment-parser/-/comment-parser-1.4.1.tgz", + "integrity": "sha512-buhp5kePrmda3vhc5B9t7pUQXAb2Tnd0qgpkIhPhkHXxJpiPJ11H0ZEU0oBpJ2QztSbzG/ZxMj/CHsYJqRHmyg==", "dev": true, "engines": { "node": ">= 12.0.0" @@ -9080,32 +8259,80 @@ } }, "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==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", + "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", "dev": true, "dependencies": { - "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" }, "engines": { - "node": ">= 10" + "node": ">= 14" + } + }, + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "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, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "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==", + "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/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": { + "safe-buffer": "~5.2.0" } }, "node_modules/concat-map": { @@ -9381,51 +8608,72 @@ } }, "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==", + "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": { "crc-32": "^1.2.0", - "readable-stream": "^3.4.0" + "readable-stream": "^4.0.0" }, "engines": { - "node": ">= 10" + "node": ">= 14" + } + }, + "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": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" } }, "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==", + "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/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/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": { - "node-fetch": "2.6.7" + "safe-buffer": "~5.2.0" } }, "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==", - "dev": true, + "extraneous": true, "dependencies": { "whatwg-url": "^5.0.0" }, @@ -9670,9 +8918,9 @@ "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==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "dependencies": { "ms": "2.1.2" }, @@ -9739,37 +8987,10 @@ "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "dev": true, - "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/deep-eql": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "dependencies": { "type-detect": "^4.0.0" @@ -9816,15 +9037,6 @@ "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/deepmerge-ts": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-5.1.0.tgz", @@ -9846,6 +9058,15 @@ "node": ">=0.10.0" } }, + "node_modules/default-compare/node_modules/kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/default-resolution": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/default-resolution/-/default-resolution-2.0.0.tgz", @@ -9860,6 +9081,7 @@ "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", "dev": true, + "optional": true, "dependencies": { "clone": "^1.0.2" }, @@ -9872,19 +9094,11 @@ "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", "dev": true, + "optional": true, "engines": { "node": ">=0.8" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", @@ -10042,18 +9256,6 @@ "node": ">=0.10.0" } }, - "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==", - "dev": true, - "dependencies": { - "repeating": "^2.0.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/detect-newline": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", @@ -10063,167 +9265,129 @@ "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==", - "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" - }, - "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_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==", - "dev": true, - "dependencies": { - "defer-to-connect": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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==", "dev": true, - "dependencies": { - "undici-types": "~5.26.4" - } + "optional": true, + "peer": true }, - "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/di": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", + "integrity": "sha512-uJaamHkagcZtHPqCIHZxnFrXlunQXgBOsZSUOWwFw31QJCAbyTBoHMW75YOTur5ZNx8pIeAKgf6GWIgaqqiLhA==", "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/diff": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.2.0.tgz", + "integrity": "sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==", "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.3.1" } }, - "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/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": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, - "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==", - "dev": true, - "engines": { - "node": ">=12.0.0" - } + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" }, - "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/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", "dev": true, "dependencies": { - "@types/node": "^18.0.0", - "got": "^11.8.1" + "esutils": "^2.0.2" }, "engines": { - "node": ">=12.0.0" - }, - "peerDependencies": { - "typescript": "^4.6.2" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "node": ">=6.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/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": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "esutils": "^2.0.2" }, "engines": { - "node": ">=12.0.0" + "node": ">=0.10.0" } }, - "node_modules/devtools/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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": { - "color-convert": "^2.0.1" + "@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": ">=8" + "node": ">=14" }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "optionalDependencies": { + "@vue/compiler-sfc": "^3.2.37", + "vue-template-compiler": "^2.7.8" } }, - "node_modules/devtools/node_modules/brace-expansion": { + "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==", @@ -10232,119 +9396,39 @@ "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" - } - }, - "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==", - "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==", + "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, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, "engines": { - "node": ">=10" + "node": "^12.17.0 || ^14.13 || >=16.0.0" }, "funding": { "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/devtools/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/devtools/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/devtools/node_modules/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, - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "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==", - "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==", - "dev": true, - "dependencies": { - "@types/which": "^1.3.2", - "which": "^2.0.2" - } - }, - "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/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": { - "pump": "^3.0.0" + "locate-path": "^7.1.0", + "path-exists": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devtools/node_modules/glob": { + "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==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -10360,82 +9444,70 @@ "url": "https://github.com/sponsors/isaacs" } }, - "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/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": { - "@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" - }, - "engines": { - "node": ">=10.19.0" + "lru-cache": "^6.0.0" }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/devtools/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, "engines": { - "node": ">=8" + "node": ">=10" } }, - "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/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": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=10.19.0" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "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/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": { - "agent-base": "6", - "debug": "4" + "p-locate": "^6.0.0" }, "engines": { - "node": ">= 6" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/devtools/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/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/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, + "dependencies": { + "yallist": "^4.0.0" + }, "engines": { - "node": ">=8" + "node": ">=10" } }, - "node_modules/devtools/node_modules/minimatch": { + "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==", @@ -10447,489 +9519,43 @@ "node": ">=10" } }, - "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==", + "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": { - "whatwg-url": "^5.0.0" + "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": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "node": ">=10" } }, - "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==", + "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": { + "yocto-queue": "^1.0.0" + }, "engines": { - "node": ">=10" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "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, - "engines": { - "node": ">=8" - } - }, - "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/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": { - "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_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==", - "dev": true, - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "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" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/devtools/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/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/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==", - "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/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_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==", - "dev": true, - "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" - }, - "engines": { - "node": ">= 8" - } - }, - "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==", - "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/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_modules/dlv": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", - "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/doctrine-temporary-fork": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine-temporary-fork/-/doctrine-temporary-fork-2.1.0.tgz", - "integrity": "sha512-nliqOv5NkE4zMON4UA6AMJE6As35afs8aYXATpU4pTUdIKiARZwrJVEP1boA3Rx1ZXHVkwxkhcq4VkqvsuRLsA==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/documentation": { - "version": "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" - }, - "engines": { - "node": ">=14" - }, - "optionalDependencies": { - "@vue/compiler-sfc": "^3.2.37", - "vue-template-compiler": "^2.7.8" - } - }, - "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": { - "balanced-match": "^1.0.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, - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "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": { - "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/sindresorhus" - } - }, - "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": { - "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_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": { - "lru-cache": "^6.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "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": { - "p-locate": "^6.0.0" - }, - "engines": { - "node": "^12.20.0 || ^14.13.1 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "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, - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "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": ">=10" - } - }, - "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": { - "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/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": { - "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/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": { - "p-limit": "^4.0.0" + "p-limit": "^4.0.0" }, "engines": { "node": "^12.20.0 || ^14.13.1 || >=16.0.0" @@ -11134,9 +9760,9 @@ } }, "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==", + "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" } @@ -11300,17 +9926,18 @@ } }, "node_modules/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, "hasInstallScript": true, "dependencies": { - "@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" }, "bin": { @@ -11338,9 +9965,9 @@ } }, "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==" + "version": "1.4.802", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", + "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==" }, "node_modules/emoji-regex": { "version": "8.0.0", @@ -11361,10 +9988,36 @@ "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": ">= 0.8" } }, + "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, + "dependencies": { + "iconv-lite": "^0.6.3", + "whatwg-encoding": "^3.1.1" + }, + "funding": { + "url": "https://github.com/fb55/encoding-sniffer?sponsor=1" + } + }, + "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": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.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", @@ -11375,10 +10028,11 @@ } }, "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==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, + "license": "MIT", "dependencies": { "@types/cookie": "^0.4.1", "@types/cors": "^2.8.12", @@ -11389,7 +10043,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "engines": { "node": ">=10.2.0" @@ -11414,9 +10068,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "dependencies": { "graceful-fs": "^4.2.4", @@ -11731,6 +10385,45 @@ "es6-symbol": "^3.1.1" } }, + "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, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "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/escalade": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", @@ -11900,1636 +10593,862 @@ "engines": { "node": "^10.12.0 || >=12.0.0" }, - "funding": { - "url": "https://opencollective.com/eslint" - } - }, - "node_modules/eslint-config-standard": { - "version": "10.2.1", - "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", - "integrity": "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/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, - "dependencies": { - "debug": "^3.2.7", - "is-core-module": "^2.13.0", - "resolve": "^1.22.4" - } - }, - "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, - "dependencies": { - "ms": "^2.1.1" - } - }, - "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==", - "dev": true, - "dependencies": { - "debug": "^3.2.7" - }, - "engines": { - "node": ">=4" - }, - "peerDependenciesMeta": { - "eslint": { - "optional": true - } - } - }, - "node_modules/eslint-module-utils/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/eslint-plugin-es": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz", - "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==", - "dev": true, - "dependencies": { - "eslint-utils": "^2.0.0", - "regexpp": "^3.0.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - }, - "peerDependencies": { - "eslint": ">=4.19.1" - } - }, - "node_modules/eslint-plugin-import": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.29.1.tgz", - "integrity": "sha512-BbPC0cuExzhiMo4Ff1BTVwHpjjv28C5R+btTOGaCRC7UEz801up0JadwkeSk5Ued6TG34uaczuVuH6qyy5YUxw==", - "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" - }, - "peerDependencies": { - "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" - } - }, - "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.1.1" - } - }, - "node_modules/eslint-plugin-import/node_modules/doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/eslint-plugin-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==", - "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" - }, - "engines": { - "node": "^12 || ^14 || ^16 || ^17" - }, - "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_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": ">=10" - } - }, - "node_modules/eslint-plugin-node": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz", - "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==", - "dev": true, - "dependencies": { - "eslint-plugin-es": "^3.0.0", - "eslint-utils": "^2.0.0", - "ignore": "^5.1.1", - "minimatch": "^3.0.4", - "resolve": "^1.10.1", - "semver": "^6.1.0" - }, - "engines": { - "node": ">=8.10.0" - }, - "peerDependencies": { - "eslint": ">=5.16.0" - } - }, - "node_modules/eslint-plugin-node/node_modules/ignore": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz", - "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==", - "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==", - "dev": true, - "engines": { - "node": "^10.12.0 || >=12.0.0" - }, - "peerDependencies": { - "eslint": "^7.0.0" - } - }, - "node_modules/eslint-plugin-standard": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.1.0.tgz", - "integrity": "sha512-fVcdyuKRr0EZ4fjWl3c+gp1BANFJD1+RaWa2UPYfMZ6jCtp5RG00kSaXnK/dE5sYzt4kaWJ9qdxqUfc0d9kX0w==", - "dev": true, - "peerDependencies": { - "eslint": ">=3.19.0" - } - }, - "node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/eslint-utils": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz", - "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==", - "dev": true, - "dependencies": { - "eslint-visitor-keys": "^1.1.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/mysticatea" - } - }, - "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/eslint-visitor-keys": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/eslint/node_modules/@babel/code-frame": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz", - "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==", - "dev": true, - "dependencies": { - "@babel/highlight": "^7.10.4" - } - }, - "node_modules/eslint/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/eslint/node_modules/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/eslint/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/eslint/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/eslint/node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/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": ">=10" - } - }, - "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, - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "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, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eslint/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/eslint/node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/esniff": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz", - "integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==", - "dev": true, - "dependencies": { - "d": "^1.0.1", - "es5-ext": "^0.10.62", - "event-emitter": "^0.3.5", - "type": "^2.7.2" - }, - "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==", - "dev": true, - "dependencies": { - "acorn": "^7.4.0", - "acorn-jsx": "^5.3.1", - "eslint-visitor-keys": "^1.3.0" - }, - "engines": { - "node": "^10.12.0 || >=12.0.0" - } - }, - "node_modules/espree/node_modules/eslint-visitor-keys": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", - "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/esprima": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", - "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", - "dev": true, - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "dependencies": { - "estraverse": "^5.1.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "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, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", - "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "dev": true, - "dependencies": { - "estraverse": "^5.2.0" - }, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/esrecurse/node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "engines": { - "node": ">=4.0" - } - }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "optional": true - }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "engines": { - "node": ">=0.10.0" - } - }, - "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, - "dependencies": { - "d": "1", - "es5-ext": "~0.10.14" - } - }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==", - "dev": true, - "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/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, - "engines": { - "node": ">=6" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==", - "dev": true - }, - "node_modules/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "dev": true, - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dev": true, - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "engines": { - "node": ">=6" - } - }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "dev": true, - "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "engines": { - "node": ">=4.8" + "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.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "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.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "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.9.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", + "tsconfig-paths": "^3.15.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=4" + }, + "peerDependencies": { + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8" } }, - "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" + "node": ">=10" + } + }, + "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, + "dependencies": { + "ansi-regex": "^5.0.1" }, - "peerDependencies": { - "typescript": ">= 4.7.4" + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=8" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "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/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": { - "debug": "4" + "has-flag": "^4.0.0" }, "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/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, - "dependencies": { - "mitt": "3.0.0" + "engines": { + "node": ">=10" }, - "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/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": { - "node-fetch": "^2.6.11" + "d": "^1.0.1", + "es5-ext": "^0.10.62", + "event-emitter": "^0.3.5", + "type": "^2.7.2" + }, + "engines": { + "node": ">=0.10" } }, - "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==", - "dev": true, - "optional": true, - "peer": true - }, - "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/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": { - "@tootallnate/once": "2", - "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/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/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, - "peer": true, - "dependencies": { - "agent-base": "6", - "debug": "4" - }, "engines": { - "node": ">= 6" + "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.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "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-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 +11467,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 +11740,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 +11822,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" + "is-unicode-supported": "^2.0.0" }, "engines": { - "node": ">=14" - }, - "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 +11886,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 +11898,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 +11922,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 +12110,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 +12280,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 +12336,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 +12395,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", @@ -14451,6 +12418,7 @@ "version": "2.2.8", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.2.8.tgz", "integrity": "sha512-R5KMKHnPAQaZMqLOsyuyUmcIjSeDm+73eoqQpaXA7AZ22BL+6C+1mcUscgOsNd8WVlJuvlgAPsegcx7pjlV0Dg==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, "bin": { "rimraf": "bin.js" @@ -14531,6 +12499,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", @@ -14546,6 +12515,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -14566,6 +12536,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" @@ -14636,19 +12607,19 @@ } }, "node_modules/geckodriver": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.0.tgz", - "integrity": "sha512-Y/Np2VkAhBkJoFAIY3pKH3rICUcR5rH9VD6EHwh0CqUIh6Opzr/NFwfcQenYfbRT/659R15/35LpA1s6h9wPPg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.1.tgz", + "integrity": "sha512-nnAdIrwLkMcDu4BitWXF23pEMeZZ0Cj7HaWWFdSpeedBP9z6ft150JYiGO2mwzw6UiR823Znk1JeIf07RyzloA==", "dev": true, "hasInstallScript": true, "dependencies": { "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", "decamelize": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.4", "node-fetch": "^3.3.2", "tar-fs": "^3.0.6", - "unzipper": "^0.11.4", "which": "^4.0.0" }, "bin": { @@ -14791,6 +12762,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 +12892,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" } @@ -14967,6 +12948,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -15094,7 +13076,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", @@ -15144,7 +13125,7 @@ "version": "1.2.13", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", - "deprecated": "The v1 package contains DANGEROUS / INSECURE binaries. Upgrade to safe fsevents v2", + "deprecated": "Upgrade to fsevents v2 to mitigate potential security issues", "dev": true, "hasInstallScript": true, "optional": true, @@ -15220,25 +13201,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 +13274,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", @@ -15339,9 +13311,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "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" @@ -15465,6 +13437,7 @@ "version": "7.1.7", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz", "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -15516,43 +13489,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", @@ -15602,6 +13538,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -15622,6 +13559,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 +13886,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", @@ -16320,6 +14147,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -16524,6 +14352,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" @@ -17746,19 +15575,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 +15624,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.2.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.2.1.tgz", + "integrity": "sha512-HoomFHQ3av1uhq+7FxJTq4Ns0clAD+tGbQNrSd0WFY3UAjjUk6G3LaWEqdgmIXYkY4pexZiyZ3ykZJhQlM0J5A==", "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 +15724,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 +15738,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 +15786,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 +15846,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 +15868,21 @@ } }, "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": "10.1.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.1.8.tgz", + "integrity": "sha512-syxGpOzLyqVeZi1KDBjRTnCn5PiGWySGHP0BbqXbqsEK0ckkZk3egAepEWslUjZXj0rhkUapVXM/IpADWe4D6w==", "dev": true, "dependencies": { - "@ljharb/through": "^2.3.11", + "@inquirer/prompts": "^5.3.8", + "@inquirer/type": "^1.5.2", + "@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 +15908,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 +16091,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 +16205,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 +16247,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 +16490,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 +16771,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 +16846,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" }, @@ -19625,9 +17393,9 @@ "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" @@ -19715,6 +17483,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", @@ -19865,6 +17645,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -19932,6 +17713,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" @@ -20136,6 +17918,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -20236,6 +18019,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -20333,9 +18117,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 +18152,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 +18223,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 +18263,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", @@ -20741,18 +18497,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 +18506,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 +18536,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 +18706,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 +18715,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", @@ -21077,15 +18779,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 +18816,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 +18988,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 +19332,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 +19364,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 +19949,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 +20001,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,19 +20032,21 @@ } }, "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 + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "optional": true, + "peer": true }, "node_modules/mixin-deep": { "version": "1.3.2", @@ -22406,31 +20080,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.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "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 +20114,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 +20172,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 +20201,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", @@ -22604,6 +20233,7 @@ "version": "8.1.0", "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -22684,9 +20314,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 +20436,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" @@ -22961,9 +20591,9 @@ } }, "node_modules/nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true, "optional": true }, @@ -23030,15 +20660,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 +20749,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" @@ -23260,18 +20881,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 +21266,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 +21275,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 +21296,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 +21305,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 +21398,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 +21436,19 @@ "node": ">=0.8" } }, + "node_modules/parse-imports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", + "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "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 +21531,55 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "node_modules/parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "dependencies": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, + "node_modules/parse5-htmlparser2-tree-adapter/node_modules/parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.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.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "dependencies": { + "entities": "^4.4.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", @@ -24150,18 +21673,15 @@ } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "dev": true, - "engines": { - "node": "14 || >=16.14" - } + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true }, "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", @@ -24272,6 +21792,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 +21962,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 +22151,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 +22172,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 +22203,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", @@ -24941,9 +22358,9 @@ } }, "node_modules/read-pkg/node_modules/type-fest": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz", - "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.0.tgz", + "integrity": "sha512-MBh+PHUHHisjXf4tlx0CFWoMdjx8zCMLJHOjnV1prABYZFHqtFOyauCIK2/7w4oIfwkF8iNhLtnJEfVY2vn3iw==", "dev": true, "engines": { "node": ">=16" @@ -25143,15 +22560,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 +22755,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 +22886,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 +22920,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 +22936,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", @@ -25590,9 +22974,9 @@ } }, "node_modules/rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "node_modules/rgb2hex": { @@ -25605,6 +22989,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" @@ -25620,6 +23005,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -25664,9 +23050,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.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", "dev": true }, "node_modules/sade": { @@ -25832,23 +23218,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 +23245,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 +23443,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 +23620,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", @@ -26191,14 +23716,11 @@ "node": ">= 10" } }, - "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==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } + "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", @@ -26429,13 +23951,14 @@ } }, "node_modules/socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, + "license": "MIT", "dependencies": { "debug": "~4.3.4", - "ws": "~8.11.0" + "ws": "~8.17.1" } }, "node_modules/socket.io-parser": { @@ -26527,15 +24050,6 @@ "decode-uri-component": "^0.2.0" } }, - "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==", - "dev": true, - "dependencies": { - "source-map": "^0.5.6" - } - }, "node_modules/source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -26605,9 +24119,9 @@ } }, "node_modules/spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "node_modules/split": { @@ -26857,13 +24371,14 @@ } }, "node_modules/streamx": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", - "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "dependencies": { - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" }, "optionalDependencies": { "bare-events": "^2.2.0" @@ -27078,12 +24593,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,6 +24616,12 @@ "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", @@ -27133,6 +24654,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", @@ -27150,9 +24693,9 @@ } }, "node_modules/table/node_modules/ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "dependencies": { "fast-deep-equal": "^3.1.3", @@ -27230,6 +24773,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -27250,6 +24794,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" @@ -27281,9 +24826,9 @@ } }, "node_modules/terser": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", - "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", "dev": true, "dependencies": { "@jridgewell/source-map": "^0.3.3", @@ -27366,15 +24911,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/terser-webpack-plugin/node_modules/serialize-javascript": { - "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" - } - }, "node_modules/terser/node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -27424,6 +24960,7 @@ "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", "dev": true, "dependencies": { "fs.realpath": "^1.0.0", @@ -27440,6 +24977,15 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "dependencies": { + "b4a": "^1.6.4" + } + }, "node_modules/text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -27517,13 +25063,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 +25103,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", @@ -27706,12 +25264,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 +25283,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 +25337,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", @@ -27813,9 +25375,9 @@ "dev": true }, "node_modules/type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "dev": true }, "node_modules/type-check": { @@ -27964,9 +25526,9 @@ } }, "node_modules/ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "dev": true, "funding": [ { @@ -27987,9 +25549,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 +25631,15 @@ "integrity": "sha512-Ia0sQNrMPXXkqVFt6w6M1n1oKo3NfKs+mvaV811Jwir7vAk9a6PVV9VPYf6X3BU97QiLEmuW3uXH9u87zDFfdw==", "dev": true }, + "node_modules/undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "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 +25682,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", @@ -28335,34 +25918,6 @@ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true }, - "node_modules/unzipper": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.11.6.tgz", - "integrity": "sha512-anERl79akvqLbAxfjIFe4hK0wsi0fH4uGLwNEl4QEnG+KKs3QQeApYgOS/f6vH2EdACUlZg35psmd/3xL2duFQ==", - "dev": true, - "dependencies": { - "big-integer": "^1.6.17", - "bluebird": "~3.4.1", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2" - } - }, - "node_modules/unzipper/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/unzipper/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/upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -28450,20 +26005,11 @@ "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true }, - "node_modules/url/node_modules/qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "dependencies": { - "side-channel": "^1.0.6" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "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", @@ -28733,13 +26279,13 @@ } }, "node_modules/video.js": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.5.tgz", - "integrity": "sha512-WRq86tXZKrThA9mK+IR+v4tIQVVvnb5LhvL71fD2AX7TxVOPdaeK1X/wyuUruBqWaOG3w2sZXoMY6HF2Jlo9qA==", + "version": "7.21.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.6.tgz", + "integrity": "sha512-m41TbODrUCToVfK1aljVd296CwDQnCRewpIm5tTXMuV87YYSGw1H+VDOaV45HlpcWSsTWWLF++InDgGJfthfUw==", "dev": true, "dependencies": { "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.16.2", + "@videojs/http-streaming": "2.16.3", "@videojs/vhs-utils": "^3.0.4", "@videojs/xhr": "2.6.0", "aes-decrypter": "3.1.3", @@ -28783,29 +26329,27 @@ "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": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.1.tgz", - "integrity": "sha512-HNfIHlrdzveYjWhGx23tPvI1TxZdrXIEfUdm55azcTniDDuIR/qJYVdkOdv7q7PqHvJaF89GmpJDeGBzd9s25w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.2.tgz", + "integrity": "sha512-8YgNq/iL17RLTXpfWAkuhM0Sq4w/x5YPVaNbUycjfqqGL/bp3Nrmc2W0qkPfh0ryB7r4cHfJbtHYP7zlW3ZkdQ==", "dev": true, "dependencies": { "global": "^4.3.2", @@ -29084,6 +26628,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,525 +26653,551 @@ } }, "node_modules/webdriver": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.36.1.tgz", - "integrity": "sha512-547RivYCHStVqtiGQBBcABAkzJbPnAWsxpXGzmj5KL+TOM2JF41N2iQRtUxXqr0jme1Nzzye7WS7Y7iSnK6i1g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.5.tgz", + "integrity": "sha512-+xkdfbmG1IZrXxiPwab450Xuh9QClOcxTJ6tnde0rzxlPxdUqZqzwuMtM+VXZybxF4yCLrJWbeT0BpwJFAz1nA==", "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.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", "ws": "^8.8.0" }, "engines": { - "node": "^16.13 || >=18" + "node": ">=18" } }, - "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" + "node_modules/webdriver/node_modules/@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "dependencies": { + "debug": "^4.3.6", + "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": ">=12.0.0" + "node": ">=18" } }, - "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==", + "node_modules/webdriver/node_modules/@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "dev": true, - "engines": { - "node": ">=10" + "dependencies": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "engines": { + "node": ">=18" } }, - "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/@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "dev": true, "dependencies": { - "defer-to-connect": "^2.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=10" + "node": ">=18" } }, - "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==", + "node_modules/webdriver/node_modules/@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "dependencies": { - "undici-types": "~5.26.4" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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": ">=18" } }, - "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==", + "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": { - "@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.4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "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/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": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" - }, "engines": { - "node": ">=12.0.0" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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==", + "node_modules/webdriver/node_modules/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, "engines": { - "node": ">=12.0.0" + "node": ">=16.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/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": { - "@wdio/utils": "7.33.0" + "agent-base": "^7.0.2", + "debug": "4" }, "engines": { - "node": ">=12.0.0" + "node": ">= 14" } }, - "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/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, - "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" } }, - "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/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": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "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": ">=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/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "color-convert": "^2.0.1" + "bin": { + "semver": "bin/semver.js" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "node": ">=10" } }, - "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/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/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/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": { + "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": ">=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/webdriverio": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.9.tgz", + "integrity": "sha512-IwvKzhcJ9NjOL55xwj27uTTKkfxsg77dmAfqoKFSP5dQ70JzU+NgxiALEjjWQDybtt1yGIkHk7wjjxjboMU1uw==", + "dev": true, + "dependencies": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.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.2.1", + "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.0.8" + }, + "engines": { + "node": ">=18.20.0" + }, + "peerDependencies": { + "puppeteer-core": "^22.3.0" + }, + "peerDependenciesMeta": { + "puppeteer-core": { + "optional": true + } + } + }, + "node_modules/webdriverio/node_modules/@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "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" + "debug": "^4.3.6", + "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": ">=8" + "node": ">=18" } }, - "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/webdriverio/node_modules/@wdio/config": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.8.tgz", + "integrity": "sha512-37L+hd+A1Nyehd/pgfTrLC6w+Ngbu0CIoFh9Vv6v8Cgu5Hih0TLofvlg+J1BNbcTd5eQ2tFKZBDeFMhQaIiTpg==", "dev": true, "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=18.20.0" } }, - "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/webdriverio/node_modules/@wdio/logger": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.8.tgz", + "integrity": "sha512-uIyYIDBwLczmsp9JE5hN3ME8Xg+9WNBfSNXD69ICHrY9WPTzFf94UeTuavK7kwSKF3ro2eJbmNZItYOfnoovnw==", "dev": true, "dependencies": { - "color-name": "~1.1.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=7.0.0" + "node": ">=18.20.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==", + "node_modules/webdriverio/node_modules/@wdio/protocols": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.8.tgz", + "integrity": "sha512-xRH54byFf623/w/KW62xkf/C2mGyigSfMm+UT3tNEAd5ZA9X2VAWQWQBPzdcrsck7Fxk4zlQX8Kb34RSs7Cy4Q==", "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/webdriverio/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, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=14.14" + "node": ">=18.20.0" } }, - "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==", + "node_modules/webdriverio/node_modules/@wdio/types": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.8.tgz", + "integrity": "sha512-pmz2iRWddTanrv8JC7v3wUGm17KRv2WyyJhQfklMSANn9V1ep6pw1RJG2WJnKq4NojMvH1nVv1sMZxXrYPhpYw==", "dev": true, "dependencies": { - "pump": "^3.0.0" + "@types/node": "^20.1.0" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=18.20.0" } }, - "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/@wdio/utils": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.8.tgz", + "integrity": "sha512-p3EgOdkhCvMxJFd3WTtSChqYFQu2mz69/5tOsljDaL+4QYwnRR7O8M9wFsL3/9XMVcHdnC4Ija2VRxQ/lb+hHQ==", "dev": true, "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^5.0.1", - "once": "^1.3.0" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "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" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=18.20.0" } }, - "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/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": { - "brace-expansion": "^2.0.1" + "debug": "^4.3.4" }, "engines": { - "node": ">=10" + "node": ">= 14" } }, - "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/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": { - "@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" - }, - "engines": { - "node": ">=10.19.0" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "balanced-match": "^1.0.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/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": ">=8" + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "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/deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "dev": true, - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, "engines": { - "node": ">=10.19.0" + "node": ">=16.0.0" } }, - "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/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": { - "universalify": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "4" }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" - } - }, - "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==", - "dev": true, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sindresorhus/ky?sponsor=1" + "node": ">= 14" } }, - "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/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/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==", + "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": ">=10" + "node": ">=16 || 14 >=14.17" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "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/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, - "engines": { - "node": ">=10" + "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" - } - }, - "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==", - "dev": true, "engines": { - "node": ">=8" + "node": ">= 14" } }, - "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/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "dependencies": { - "lowercase-keys": "^2.0.0" + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sponsors/isaacs" } }, - "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/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": { - "ansi-regex": "^5.0.1" + "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/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/webdriver": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.8.tgz", + "integrity": "sha512-UnV0ANriSTUgypGk0pz8lApeQuHt+72WEDQG5hFwkkSvggtKLyWdT7+PQkNoXvDajTmiLIqUOq8XPI/Pm71rtw==", "dev": true, "dependencies": { - "has-flag": "^4.0.0" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "deepmerge-ts": "^7.0.3", + "ws": "^8.8.0" }, "engines": { - "node": ">=8" + "node": ">=18.20.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", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "dependencies": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -29718,10 +27289,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" }, @@ -29910,10 +27482,10 @@ "node": ">=0.4.0" } }, - "node_modules/webpack/node_modules/acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "node_modules/webpack/node_modules/acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "peerDependencies": { "acorn": "^8" @@ -29982,14 +27554,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 +27705,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": { @@ -30256,16 +27851,17 @@ } }, "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, "peerDependencies": { "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "utf-8-validate": ">=5.0.2" }, "peerDependenciesMeta": { "bufferutil": { @@ -30387,73 +27983,102 @@ "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/zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/colinhacks" } }, "node_modules/zwitch": { @@ -30484,34 +28109,34 @@ } }, "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.24.7.tgz", + "integrity": "sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==", "requires": { - "@babel/highlight": "^7.24.2", + "@babel/highlight": "^7.24.7", "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.25.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.25.4.tgz", + "integrity": "sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ==" }, "@babel/core": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.24.5.tgz", - "integrity": "sha512-tVQRucExLQ02Boi4vdPp49svNGcfL2GhdTCT9aldhXgCJVAI21EtRfBettiuLUwce/7r6bFdgs6JFkcdTiFttA==", + "version": "7.25.2", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.25.2.tgz", + "integrity": "sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA==", "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.24.7", + "@babel/generator": "^7.25.0", + "@babel/helper-compilation-targets": "^7.25.2", + "@babel/helper-module-transforms": "^7.25.2", + "@babel/helpers": "^7.25.0", + "@babel/parser": "^7.25.0", + "@babel/template": "^7.25.0", + "@babel/traverse": "^7.25.2", + "@babel/types": "^7.25.2", "convert-source-map": "^2.0.0", "debug": "^4.1.0", "gensync": "^1.0.0-beta.2", @@ -30520,9 +28145,9 @@ } }, "@babel/eslint-parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.5.tgz", - "integrity": "sha512-gsUcqS/fPlgAw1kOtpss7uhY6E9SFFANQ6EFX5GTvzUwaV0+sGaZWk6xq22MOdeT9wfxyokW3ceCUvOiRtZciQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.24.7.tgz", + "integrity": "sha512-SO5E3bVxDuxyNxM5agFv480YA2HO6ohZbGxbazZdIk3KQOPOGVNw6q78I9/lbviIf95eq6tPozeYnJLbjnC8IA==", "dev": true, "requires": { "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1", @@ -30531,66 +28156,67 @@ } }, "@babel/generator": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.24.5.tgz", - "integrity": "sha512-x32i4hEXvr+iI0NEoEfDKzlemF8AmtOP8CcrRaEcpzysWuoEb1KknpcvMsHKPONoKZiDuItklgWhB18xEhr9PA==", + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.25.6.tgz", + "integrity": "sha512-VPC82gr1seXOpkjAAKoLhP50vx4vGNlF4msF64dSFq1P8RfB+QAuJWGHPXXPc8QyfVWwwB/TNNU4+ayZmHNbZw==", "requires": { - "@babel/types": "^7.24.5", + "@babel/types": "^7.25.6", "@jridgewell/gen-mapping": "^0.3.5", "@jridgewell/trace-mapping": "^0.3.25", "jsesc": "^2.5.1" } }, "@babel/helper-annotate-as-pure": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.22.5.tgz", - "integrity": "sha512-LvBTxu8bQSQkcyKOU+a1btnNFQ1dMAd0R6PyW3arXes06F6QLWLIrd681bxRPIXlrMGR3XYnW9JyML7dP3qgxg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz", + "integrity": "sha512-BaDeOonYvhdKw+JoMVkAixAAJzG2jVPIwWoKBPdYuY9b452e2rPuI9QPYh3KpofZ3pW2akOmwZLOiOsHMiqRAg==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-builder-binary-assignment-operator-visitor": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.22.15.tgz", - "integrity": "sha512-QkBXwGgaoC2GtGZRoma6kv7Szfv06khvhFav67ZExau2RaXzy8MpHSMO2PNoP2XtmQphJQRHFfg77Bq731Yizw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.24.7.tgz", + "integrity": "sha512-xZeCVVdwb4MsDBkkyZ64tReWYrLRHlMN72vP7Bdm3OUOuyFZExhsHUUnuWnm2/XOlAJzR0LfPpB56WXZn0X/lA==", "requires": { - "@babel/types": "^7.22.15" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@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.2", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz", + "integrity": "sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw==", "requires": { - "@babel/compat-data": "^7.23.5", - "@babel/helper-validator-option": "^7.23.5", - "browserslist": "^4.22.2", + "@babel/compat-data": "^7.25.2", + "@babel/helper-validator-option": "^7.24.8", + "browserslist": "^4.23.1", "lru-cache": "^5.1.1", "semver": "^6.3.1" } }, "@babel/helper-create-class-features-plugin": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.5.tgz", - "integrity": "sha512-uRc4Cv8UQWnE4NXlYTIIdM7wfFkOqlFztcC/gVXDKohKoVB3OyonfelUBaJzSwpBntZ2KYGF/9S7asCHsXwW6g==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-member-expression-to-functions": "^7.24.5", - "@babel/helper-optimise-call-expression": "^7.22.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz", + "integrity": "sha512-kTkaDl7c9vO80zeX1rJxnuRpEsD5tA81yh11X1gQo+PhSti3JS+7qeZo9U4RHobKRiFPKaGK3svUAeb8D0Q7eg==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "semver": "^6.3.1" } }, "@babel/helper-create-regexp-features-plugin": { - "version": "7.22.15", - "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.22.15.tgz", - "integrity": "sha512-29FkPLFjn4TPEa3RE7GpW+qbE8tlsu3jntNYNfcGsc49LphF1PQIiD+vMZ1z1xVOKt+93khA9tc2JBs3kBjA7w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.24.7.tgz", + "integrity": "sha512-03TCmXy2FtXJEZfbXDTSqq1fRJArk7lX9DOFC/47VthYcxyIOx+eXQmdo6DOQvrbpIix+KfXwvuXdFDZHxt+rA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", + "@babel/helper-annotate-as-pure": "^7.24.7", "regexpu-core": "^5.3.1", "semver": "^6.3.1" } @@ -30608,197 +28234,206 @@ } }, "@babel/helper-environment-visitor": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", - "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.24.7.tgz", + "integrity": "sha512-DoiN84+4Gnd0ncbBOM9AZENV4a5ZiL39HYMyZJGZ/AZEykHYdJw0wW3kdcsh9/Kn+BRXHLkkklZ51ecPKmI1CQ==", + "requires": { + "@babel/types": "^7.24.7" + } }, "@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": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.5.tgz", - "integrity": "sha512-4owRteeihKWKamtqg4JmWSsEZU445xpFRXPEwp44HbgbxdWlUV1b4Agg4lkA806Lil5XM/e+FJyS0vj5T6vmcA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.24.7.tgz", + "integrity": "sha512-LGeMaf5JN4hAT471eJdBs/GK1DoYIJ5GCtZN/EsL6KUiiDZOvO/eKE11AMZJa2zP4zk4qe9V2O/hxAmkRc8p6w==", "requires": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.24.7.tgz", + "integrity": "sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==", "requires": { - "@babel/types": "^7.24.0" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@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.25.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz", + "integrity": "sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ==", "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.24.7", + "@babel/helper-simple-access": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7", + "@babel/traverse": "^7.25.2" } }, "@babel/helper-optimise-call-expression": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz", - "integrity": "sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz", + "integrity": "sha512-jKiTsW2xmWwxT1ixIdfXUZp+P5yURx2suzLZr5Hi64rURpDYdMW0pv+Uf17EYk2Rd428Lx4tLsnjGJzYKDM/6A==", "requires": { - "@babel/types": "^7.22.5" + "@babel/types": "^7.24.7" } }, "@babel/helper-plugin-utils": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.5.tgz", - "integrity": "sha512-xjNLDopRzW2o6ba0gKbkZq5YWEBaK3PCyTOY1K2P/O07LGMhMqlMXPxwN4S5/RhWuCobT8z0jrlKGlYmeR1OhQ==" + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.24.7.tgz", + "integrity": "sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==" }, "@babel/helper-remap-async-to-generator": { - "version": "7.22.20", - "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.22.20.tgz", - "integrity": "sha512-pBGyV4uBqOns+0UvhsTO8qgl8hO89PmiDYv+/COyp1aeMcmfrfruz+/nCMFiYyFF/Knn0yfrC85ZzNFjembFTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.24.7.tgz", + "integrity": "sha512-9pKLcTlZ92hNZMQfGCHImUpDOlAgkkpqalWEeftW5FBya75k8Li2ilerxkM/uBEj01iBZXcCIB/bwvDYgWyibA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-wrap-function": "^7.22.20" + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-wrap-function": "^7.24.7" } }, "@babel/helper-replace-supers": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.1.tgz", - "integrity": "sha512-QCR1UqC9BzG5vZl8BMicmZ28RuUBnHhAMddD8yHFHDRH9lLTZ9uUPehX8ctVPT8l0TKblJidqcgUUKGVrePleQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.24.7.tgz", + "integrity": "sha512-qTAxxBM81VEyoAY0TtLrx1oAEJc09ZK67Q9ljQToqCnA+55eNwCORaxlKyu+rNfX86o8OXRUSNUnrtsAZXM9sg==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-member-expression-to-functions": "^7.23.0", - "@babel/helper-optimise-call-expression": "^7.22.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-member-expression-to-functions": "^7.24.7", + "@babel/helper-optimise-call-expression": "^7.24.7" } }, "@babel/helper-simple-access": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.5.tgz", - "integrity": "sha512-uH3Hmf5q5n7n8mz7arjUlDOCbttY/DW4DYhE6FUsjKJ/oYC1kQQUvwEQWxRwUpX9qQKRXeqLwWxrqilMrf32sQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.24.7.tgz", + "integrity": "sha512-zBAIvbCMh5Ts+b86r/CjU+4XGYIs+R1j951gxI3KmmxBMhCg4oQMsv6ZXQ64XOm/cvzfU1FmoCyt6+owc5QMYg==", "requires": { - "@babel/types": "^7.24.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-skip-transparent-expression-wrappers": { - "version": "7.22.5", - "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==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.24.7.tgz", + "integrity": "sha512-IO+DLT3LQUElMbpzlatRASEyQtfhSE0+m465v++3jyyXeBTBUjtVZg28/gHeV5mrTJqvEKhKroBGAvhW+qPHiQ==", "requires": { - "@babel/types": "^7.22.5" + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@babel/helper-split-export-declaration": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.5.tgz", - "integrity": "sha512-5CHncttXohrHk8GWOFCcCl4oRD9fKosWlIRgWm4ql9VYioKm52Mk2xsmoohvm7f3JoiLSM5ZgJuRaf5QZZYd3Q==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.24.7.tgz", + "integrity": "sha512-oy5V7pD+UvfkEATUKvIjvIAH/xCzfsFVw7ygW2SI6NClZzquT+mwdTfgfdbUiceh6iQO0CHtCPsyze/MZ2YbAA==", "requires": { - "@babel/types": "^7.24.5" + "@babel/types": "^7.24.7" } }, "@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.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz", + "integrity": "sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ==" }, "@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.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz", + "integrity": "sha512-rR+PBcQ1SMQDDyF6X0wxtG8QyLCgUB0eRAGguqRLfkCA87l7yAP7ehq8SNj96OOGTO8OBV70KhuFYcIkHXOg0w==" }, "@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.24.8", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz", + "integrity": "sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q==" }, "@babel/helper-wrap-function": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.5.tgz", - "integrity": "sha512-/xxzuNvgRl4/HLNKvnFwdhdgN3cpLxgLROeLDl83Yx0AJ1SGvq1ak0OszTOjDfiB8Vx03eJbeDWh9r+jCCWttw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz", + "integrity": "sha512-N9JIYk3TD+1vq/wn77YnJOqMtfWhNewNE+DJV4puD2X7Ew9J4JvrzrFDfTfyv5EgEXVy9/Wt8QiOErzEmv5Ifw==", "requires": { - "@babel/helper-function-name": "^7.23.0", - "@babel/template": "^7.24.0", - "@babel/types": "^7.24.5" + "@babel/helper-function-name": "^7.24.7", + "@babel/template": "^7.24.7", + "@babel/traverse": "^7.24.7", + "@babel/types": "^7.24.7" } }, "@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.25.6", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.6.tgz", + "integrity": "sha512-Xg0tn4HcfTijTwfDwYlvVCl43V6h4KyVVX2aEm4qdO/PC6L2YvzLHFdmxhoeSA3eslcE6+ZVXHgWwopXYLNq4Q==", "requires": { - "@babel/template": "^7.24.0", - "@babel/traverse": "^7.24.5", - "@babel/types": "^7.24.5" + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6" } }, "@babel/highlight": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.5.tgz", - "integrity": "sha512-8lLmua6AVh/8SLJRRVD6V8p73Hir9w5mJrhE+IPpILG31KKlI9iz5zmBYKcWPS59qSfgP9RaSBQSHHE81WKuEw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.24.7.tgz", + "integrity": "sha512-EStJpq4OuY8xYfhGVXngigBJRWxftKX9ksiGDnmlY3o7B/V7KIAc9X4oiK87uPJSc/vs5L869bem5fhZa8caZw==", "requires": { - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-validator-identifier": "^7.24.7", "chalk": "^2.4.2", "js-tokens": "^4.0.0", "picocolors": "^1.0.0" } }, "@babel/parser": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.5.tgz", - "integrity": "sha512-EOv5IK8arwh3LI47dz1b0tKUb/1uhHAnHJOrjgtQMIpu1uXd9mlFrJg9IUgGUgZ41Ch0K8REPTYpO7B76b4vJg==" + "version": "7.25.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.6.tgz", + "integrity": "sha512-trGdfBdbD0l1ZPmcJ83eNxB9rbEax4ALFTF7fN386TMYbeCQbyme5cOEXQhbGXKebwGaB/J52w1mrklMcbgy6Q==", + "requires": { + "@babel/types": "^7.25.6" + } }, "@babel/plugin-bugfix-firefox-class-in-computed-class-key": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.5.tgz", - "integrity": "sha512-LdXRi1wEMTrHVR4Zc9F8OewC3vdm5h4QB6L71zy6StmYeqGi1b3ttIO8UC+BfZKcH9jdr4aI249rBkm+3+YvHw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz", + "integrity": "sha512-TiT1ss81W80eQsN+722OaeQMY/G4yTb4G9JrqeiDADs3N8lbPMGldWi9x8tyqCW5NLx1Jh2AvkE6r6QvEltMMQ==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.1.tgz", - "integrity": "sha512-y4HqEnkelJIOQGd+3g1bTeKsA5c6qM7eOn7VggGVbBc0y8MLSKHacwcIE2PplNlQSj0PqS9rrXL/nkPVK+kUNg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.24.7.tgz", + "integrity": "sha512-unaQgZ/iRu/By6tsjMZzpeBZjChYfLYry6HrEXPoz3KmfF0sVBQ1l8zKMQ4xRGLWVsjuvB8nQfjNP/DcfEOCsg==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.1.tgz", - "integrity": "sha512-Hj791Ii4ci8HqnaKHAlLNs+zaLXb0EzSDhiAWp5VNlyvCNymYfacs64pxTxbH1znW/NcArSmwpmG9IKE/TUVVQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.24.7.tgz", + "integrity": "sha512-+izXIbke1T33mY4MSNnrqhPXDz01WYhEf3yF5NbnUtkiNnm+XBZJl3kNfoK6NKmYlz/D07+l2GWVK/QfDkNCuQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", - "@babel/plugin-transform-optional-chaining": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7" } }, "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.1.tgz", - "integrity": "sha512-m9m/fXsXLiHfwdgydIFnpk+7jlVbnvlK5B2EKiPdLUb6WX654ZaaEWJUjk8TftRbZpK0XibovlLWX4KIZhV6jw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly/-/plugin-bugfix-v8-static-class-fields-redefine-readonly-7.24.7.tgz", + "integrity": "sha512-utA4HuR6F4Vvcr+o4DnjL8fCOlgRFGbeeBEGNg3ZTrLFw6VWG5XmUrvcQ0FjIYMU2ST4XcR2Wsp7t9qOAPnxMg==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-proposal-private-methods": { @@ -30858,19 +28493,19 @@ } }, "@babel/plugin-syntax-import-assertions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.1.tgz", - "integrity": "sha512-IuwnI5XnuF189t91XbxmXeCDz3qs6iDRO7GJ++wcfgeXNs/8FmIlKcpDSXNVyuLQxlwvskmI3Ct73wUODkJBlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-assertions/-/plugin-syntax-import-assertions-7.24.7.tgz", + "integrity": "sha512-Ec3NRUMoi8gskrkBe3fNmEQfxDvY8bgfQpz6jlk/41kX9eUjvpyqWU7PBP/pLAvMaSQjbMNKJmvX57jP+M6bPg==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-syntax-import-attributes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.1.tgz", - "integrity": "sha512-zhQTMH0X2nVLnb04tz+s7AMuasX8U0FnpE+nHTOhSOINjWMnopoZTxtIKsd45n4GQ/HIZLyfIpoul8e2m0DnRA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-import-attributes/-/plugin-syntax-import-attributes-7.24.7.tgz", + "integrity": "sha512-hbX+lKKeUMGihnK8nvKqmXBInriT3GVjzXKFriV3YC6APGxMbP8RZNFwy91+hocLXq90Mta+HshoB31802bb8A==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-syntax-import-meta": { @@ -30963,371 +28598,371 @@ } }, "@babel/plugin-transform-arrow-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.1.tgz", - "integrity": "sha512-ngT/3NkRhsaep9ck9uj2Xhv9+xB1zShY3tM3g6om4xxCELwCDN4g4Aq5dRn48+0hasAql7s2hdBOysCfNpr4fw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.24.7.tgz", + "integrity": "sha512-Dt9LQs6iEY++gXUwY03DNFat5C2NbO48jj+j/bSAz6b3HgPs39qcPiYt77fDObIcFwj3/C2ICX9YMwGflUoSHQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-async-generator-functions": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.3.tgz", - "integrity": "sha512-Qe26CMYVjpQxJ8zxM1340JFNjZaF+ISWpr1Kt/jGo+ZTUzKkfw/pphEWbRCb+lmSM6k/TOgfYLvmbHkUQ0asIg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-generator-functions/-/plugin-transform-async-generator-functions-7.24.7.tgz", + "integrity": "sha512-o+iF77e3u7ZS4AoAuJvapz9Fm001PuD2V3Lp6OSE4FYQke+cSewYtnek+THqGRWyQloRCyvWL1OkyfNEl9vr/g==", "requires": { - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7", "@babel/plugin-syntax-async-generators": "^7.8.4" } }, "@babel/plugin-transform-async-to-generator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.1.tgz", - "integrity": "sha512-AawPptitRXp1y0n4ilKcGbRYWfbbzFWz2NqNu7dacYDtFtz0CMjG64b3LQsb3KIgnf4/obcUL78hfaOS7iCUfw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.24.7.tgz", + "integrity": "sha512-SQY01PcJfmQ+4Ash7NE+rpbLFbmqA2GPIgqzxfFTL4t1FKRq4zTms/7htKpoCUI9OcFYgzqfmCdH53s6/jn5fA==", "requires": { - "@babel/helper-module-imports": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-remap-async-to-generator": "^7.22.20" + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-remap-async-to-generator": "^7.24.7" } }, "@babel/plugin-transform-block-scoped-functions": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.1.tgz", - "integrity": "sha512-TWWC18OShZutrv9C6mye1xwtam+uNi2bnTOCBUd5sZxyHOiWbU6ztSROofIMrK84uweEZC219POICK/sTYwfgg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.24.7.tgz", + "integrity": "sha512-yO7RAz6EsVQDaBH18IDJcMB1HnrUn2FJ/Jslc/WtPPWcjhpUJXU/rjbwmluzp7v/ZzWcEhTMXELnnsz8djWDwQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-block-scoping": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.5.tgz", - "integrity": "sha512-sMfBc3OxghjC95BkYrYocHL3NaOplrcaunblzwXhGmlPwpmfsxr4vK+mBBt49r+S240vahmv+kUxkeKgs+haCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.24.7.tgz", + "integrity": "sha512-Nd5CvgMbWc+oWzBsuaMcbwjJWAcp5qzrbg69SZdHSP7AMY0AbWFqFO0WTFCA1jxhMCwodRwvRec8k0QUbZk7RQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-class-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.1.tgz", - "integrity": "sha512-OMLCXi0NqvJfORTaPQBwqLXHhb93wkBKZ4aNwMl6WtehO7ar+cmp+89iPEQPqxAnxsOKTaMcs3POz3rKayJ72g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-properties/-/plugin-transform-class-properties-7.24.7.tgz", + "integrity": "sha512-vKbfawVYayKcSeSR5YYzzyXvsDFWU2mD8U5TFeXtbCPLFUqe7GyCgvO6XDHzje862ODrOwy6WCPmKeWHbCFJ4w==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-class-static-block": { - "version": "7.24.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.4.tgz", - "integrity": "sha512-B8q7Pz870Hz/q9UgP8InNpY01CSLDSCyqX7zcRuv3FcPl87A2G17lASroHWaCtbdIcbYzOZ7kWmXFKbijMSmFg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-class-static-block/-/plugin-transform-class-static-block-7.24.7.tgz", + "integrity": "sha512-HMXK3WbBPpZQufbMG4B46A90PkuuhN9vBCb5T8+VAHqvAqvcLi+2cKoukcpmUYkszLhScU3l1iudhrks3DggRQ==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.24.4", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-class-static-block": "^7.14.5" } }, "@babel/plugin-transform-classes": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.5.tgz", - "integrity": "sha512-gWkLP25DFj2dwe9Ck8uwMOpko4YsqyfZJrOmqqcegeDYEbp7rmn4U6UQZNj08UF6MaX39XenSpKRCvpDRBtZ7Q==", - "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-environment-visitor": "^7.22.20", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-replace-supers": "^7.24.1", - "@babel/helper-split-export-declaration": "^7.24.5", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.24.7.tgz", + "integrity": "sha512-CFbbBigp8ln4FU6Bpy6g7sE8B/WmCmzvivzUC6xDAdWVsjYTXijpuuGJmYkAaoWAzcItGKT3IOAbxRItZ5HTjw==", + "requires": { + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-environment-visitor": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7", + "@babel/helper-split-export-declaration": "^7.24.7", "globals": "^11.1.0" } }, "@babel/plugin-transform-computed-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.1.tgz", - "integrity": "sha512-5pJGVIUfJpOS+pAqBQd+QMaTD2vCL/HcePooON6pDpHgRp4gNRmzyHTPIkXntwKsq3ayUFVfJaIKPw2pOkOcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.24.7.tgz", + "integrity": "sha512-25cS7v+707Gu6Ds2oY6tCkUwsJ9YIDbggd9+cu9jzzDgiNq7hR/8dkzxWfKWnTic26vsI3EsCXNd4iEB6e8esQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/template": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/template": "^7.24.7" } }, "@babel/plugin-transform-destructuring": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.5.tgz", - "integrity": "sha512-SZuuLyfxvsm+Ah57I/i1HVjveBENYK9ue8MJ7qkc7ndoNjqquJiElzA7f5yaAXjyW2hKojosOTAQQRX50bPSVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.24.7.tgz", + "integrity": "sha512-19eJO/8kdCQ9zISOf+SEUJM/bAUIsvY3YDnXZTupUCQ8LgrWnsG/gFB9dvXqdXnRXMAM8fvt7b0CBKQHNGy1mw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-dotall-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.1.tgz", - "integrity": "sha512-p7uUxgSoZwZ2lPNMzUkqCts3xlp8n+o05ikjy7gbtFJSt9gdU88jAmtfmOxHM14noQXBxfgzf2yRWECiNVhTCw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.24.7.tgz", + "integrity": "sha512-ZOA3W+1RRTSWvyqcMJDLqbchh7U4NRGqwRfFSVbOLS/ePIP4vHB5e8T8eXcuqyN1QkgKyj5wuW0lcS85v4CrSw==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-duplicate-keys": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.1.tgz", - "integrity": "sha512-msyzuUnvsjsaSaocV6L7ErfNsa5nDWL1XKNnDePLgmz+WdU4w/J8+AxBMrWfi9m4IxfL5sZQKUPQKDQeeAT6lA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.24.7.tgz", + "integrity": "sha512-JdYfXyCRihAe46jUIliuL2/s0x0wObgwwiGxw/UbgJBr20gQBThrokO4nYKgWkD7uBaqM7+9x5TU7NkExZJyzw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-dynamic-import": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.1.tgz", - "integrity": "sha512-av2gdSTyXcJVdI+8aFZsCAtR29xJt0S5tas+Ef8NvBNmD1a+N/3ecMLeMBgfcK+xzsjdLDT6oHt+DFPyeqUbDA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dynamic-import/-/plugin-transform-dynamic-import-7.24.7.tgz", + "integrity": "sha512-sc3X26PhZQDb3JhORmakcbvkeInvxz+A8oda99lj7J60QRuPZvNAk9wQlTBS1ZynelDrDmTU4pw1tyc5d5ZMUg==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-dynamic-import": "^7.8.3" } }, "@babel/plugin-transform-exponentiation-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.1.tgz", - "integrity": "sha512-U1yX13dVBSwS23DEAqU+Z/PkwE9/m7QQy8Y9/+Tdb8UWYaGNDYwTLi19wqIAiROr8sXVum9A/rtiH5H0boUcTw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.24.7.tgz", + "integrity": "sha512-Rqe/vSc9OYgDajNIK35u7ot+KeCoetqQYFXM4Epf7M7ez3lWlOjrDjrwMei6caCVhfdw+mIKD4cgdGNy5JQotQ==", "requires": { - "@babel/helper-builder-binary-assignment-operator-visitor": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-export-namespace-from": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.1.tgz", - "integrity": "sha512-Ft38m/KFOyzKw2UaJFkWG9QnHPG/Q/2SkOrRk4pNBPg5IPZ+dOxcmkK5IyuBcxiNPyyYowPGUReyBvrvZs7IlQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-export-namespace-from/-/plugin-transform-export-namespace-from-7.24.7.tgz", + "integrity": "sha512-v0K9uNYsPL3oXZ/7F9NNIbAj2jv1whUEtyA6aujhekLs56R++JDQuzRcP2/z4WX5Vg/c5lE9uWZA0/iUoFhLTA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-export-namespace-from": "^7.8.3" } }, "@babel/plugin-transform-for-of": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.1.tgz", - "integrity": "sha512-OxBdcnF04bpdQdR3i4giHZNZQn7cm8RQKcSwA17wAAqEELo1ZOwp5FFgeptWUQXFyT9kwHo10aqqauYkRZPCAg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.24.7.tgz", + "integrity": "sha512-wo9ogrDG1ITTTBsy46oGiN1dS9A7MROBTcYsfS8DtsImMkHk9JXJ3EWQM6X2SUw4x80uGPlwj0o00Uoc6nEE3g==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" } }, "@babel/plugin-transform-function-name": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.1.tgz", - "integrity": "sha512-BXmDZpPlh7jwicKArQASrj8n22/w6iymRnvHYYd2zO30DbE277JO20/7yXJT3QxDPtiQiOxQBbZH4TpivNXIxA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.24.7.tgz", + "integrity": "sha512-U9FcnA821YoILngSmYkW6FjyQe2TyZD5pHt4EVIhmcTkrJw/3KqcrRSxuOo5tFZJi7TE19iDyI1u+weTI7bn2w==", "requires": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-function-name": "^7.23.0", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-function-name": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-json-strings": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.1.tgz", - "integrity": "sha512-U7RMFmRvoasscrIFy5xA4gIp8iWnWubnKkKuUGJjsuOH7GfbMkB+XZzeslx2kLdEGdOJDamEmCqOks6e8nv8DQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-json-strings/-/plugin-transform-json-strings-7.24.7.tgz", + "integrity": "sha512-2yFnBGDvRuxAaE/f0vfBKvtnvvqU8tGpMHqMNpTN2oWMKIR3NqFkjaAgGwawhqK/pIN2T3XdjGPdaG0vDhOBGw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-json-strings": "^7.8.3" } }, "@babel/plugin-transform-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.1.tgz", - "integrity": "sha512-zn9pwz8U7nCqOYIiBaOxoQOtYmMODXTJnkxG4AtX8fPmnCRYWBOHD0qcpwS9e2VDSp1zNJYpdnFMIKb8jmwu6g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.24.7.tgz", + "integrity": "sha512-vcwCbb4HDH+hWi8Pqenwnjy+UiklO4Kt1vfspcQYFhJdpthSnW8XvWGyDZWKNVrVbVViI/S7K9PDJZiUmP2fYQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-logical-assignment-operators": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.1.tgz", - "integrity": "sha512-OhN6J4Bpz+hIBqItTeWJujDOfNP+unqv/NJgyhlpSqgBTPm37KkMmZV6SYcOj+pnDbdcl1qRGV/ZiIjX9Iy34w==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-logical-assignment-operators/-/plugin-transform-logical-assignment-operators-7.24.7.tgz", + "integrity": "sha512-4D2tpwlQ1odXmTEIFWy9ELJcZHqrStlzK/dAOWYyxX3zT0iXQB6banjgeOJQXzEc4S0E0a5A+hahxPaEFYftsw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" } }, "@babel/plugin-transform-member-expression-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.1.tgz", - "integrity": "sha512-4ojai0KysTWXzHseJKa1XPNXKRbuUrhkOPY4rEGeR+7ChlJVKxFa3H3Bz+7tWaGKgJAXUWKOGmltN+u9B3+CVg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.24.7.tgz", + "integrity": "sha512-T/hRC1uqrzXMKLQ6UCwMT85S3EvqaBXDGf0FaMf4446Qx9vKwlghvee0+uuZcDUCZU5RuNi4781UQ7R308zzBw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-modules-amd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.1.tgz", - "integrity": "sha512-lAxNHi4HVtjnHd5Rxg3D5t99Xm6H7b04hUS7EHIXcUl2EV4yl1gWdqZrNzXnSrHveL9qMdbODlLF55mvgjAfaQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.24.7.tgz", + "integrity": "sha512-9+pB1qxV3vs/8Hdmz/CulFB8w2tuu6EB94JZFsjdqxQokwGa9Unap7Bo2gGBGIvPmDIVvQrom7r5m/TCDMURhg==", "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-modules-commonjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.1.tgz", - "integrity": "sha512-szog8fFTUxBfw0b98gEWPaEqF42ZUD/T3bkynW/wtgx2p/XCP55WEsb+VosKceRSd6njipdZvNogqdtI4Q0chw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.24.7.tgz", + "integrity": "sha512-iFI8GDxtevHJ/Z22J5xQpVqFLlMNstcLXh994xifFwxxGslr2ZXXLWgtBeLctOD63UFDArdvN6Tg8RFw+aEmjQ==", "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-simple-access": "^7.22.5" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-simple-access": "^7.24.7" } }, "@babel/plugin-transform-modules-systemjs": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.1.tgz", - "integrity": "sha512-mqQ3Zh9vFO1Tpmlt8QPnbwGHzNz3lpNEMxQb1kAemn/erstyqw1r9KeOlOfo3y6xAnFEcOv2tSyrXfmMk+/YZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.24.7.tgz", + "integrity": "sha512-GYQE0tW7YoaN13qFh3O1NCY4MPkUiAH3fiF7UcV/I3ajmDKEdG3l+UOcbAm4zUE3gnvUU+Eni7XrVKo9eO9auw==", "requires": { - "@babel/helper-hoist-variables": "^7.22.5", - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-validator-identifier": "^7.22.20" + "@babel/helper-hoist-variables": "^7.24.7", + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-identifier": "^7.24.7" } }, "@babel/plugin-transform-modules-umd": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.1.tgz", - "integrity": "sha512-tuA3lpPj+5ITfcCluy6nWonSL7RvaG0AOTeAuvXqEKS34lnLzXpDb0dcP6K8jD0zWZFNDVly90AGFJPnm4fOYg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.24.7.tgz", + "integrity": "sha512-3aytQvqJ/h9z4g8AsKPLvD4Zqi2qT+L3j7XoFFu1XBlZWEl2/1kWnhmAbxpLgPrHSY0M6UA02jyTiwUVtiKR6A==", "requires": { - "@babel/helper-module-transforms": "^7.23.3", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-module-transforms": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-named-capturing-groups-regex": { - "version": "7.22.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.22.5.tgz", - "integrity": "sha512-YgLLKmS3aUBhHaxp5hi1WJTgOUb/NCuDHzGT9z9WTt3YG+CPRhJs6nprbStx6DnWM4dh6gt7SU3sZodbZ08adQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.24.7.tgz", + "integrity": "sha512-/jr7h/EWeJtk1U/uz2jlsCioHkZk1JJZVcc8oQsJ1dUlaJD83f4/6Zeh2aHt9BIFokHIsSeDfhUmju0+1GPd6g==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.5", - "@babel/helper-plugin-utils": "^7.22.5" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-new-target": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.1.tgz", - "integrity": "sha512-/rurytBM34hYy0HKZQyA0nHbQgQNFm4Q/BOc9Hflxi2X3twRof7NaE5W46j4kQitm7SvACVRXsa6N/tSZxvPug==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.24.7.tgz", + "integrity": "sha512-RNKwfRIXg4Ls/8mMTza5oPF5RkOW8Wy/WgMAp1/F1yZ8mMbtwXW+HDoJiOsagWrAhI5f57Vncrmr9XeT4CVapA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-nullish-coalescing-operator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.1.tgz", - "integrity": "sha512-iQ+caew8wRrhCikO5DrUYx0mrmdhkaELgFa+7baMcVuhxIkN7oxt06CZ51D65ugIb1UWRQ8oQe+HXAVM6qHFjw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-nullish-coalescing-operator/-/plugin-transform-nullish-coalescing-operator-7.24.7.tgz", + "integrity": "sha512-Ts7xQVk1OEocqzm8rHMXHlxvsfZ0cEF2yomUqpKENHWMF4zKk175Y4q8H5knJes6PgYad50uuRmt3UJuhBw8pQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" } }, "@babel/plugin-transform-numeric-separator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.1.tgz", - "integrity": "sha512-7GAsGlK4cNL2OExJH1DzmDeKnRv/LXq0eLUSvudrehVA5Rgg4bIrqEUW29FbKMBRT0ztSqisv7kjP+XIC4ZMNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-numeric-separator/-/plugin-transform-numeric-separator-7.24.7.tgz", + "integrity": "sha512-e6q1TiVUzvH9KRvicuxdBTUj4AdKSRwzIyFFnfnezpCfP2/7Qmbb8qbU2j7GODbl4JMkblitCQjKYUaX/qkkwA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-numeric-separator": "^7.10.4" } }, "@babel/plugin-transform-object-rest-spread": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.5.tgz", - "integrity": "sha512-7EauQHszLGM3ay7a161tTQH7fj+3vVM/gThlz5HpFtnygTxjrlvoeq7MPVA1Vy9Q555OB8SnAOsMkLShNkkrHA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-rest-spread/-/plugin-transform-object-rest-spread-7.24.7.tgz", + "integrity": "sha512-4QrHAr0aXQCEFni2q4DqKLD31n2DL+RxcwnNjDFkSG0eNQ/xCavnRkfCUjsyqGC2OviNJvZOF/mQqZBw7i2C5Q==", "requires": { - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-object-rest-spread": "^7.8.3", - "@babel/plugin-transform-parameters": "^7.24.5" + "@babel/plugin-transform-parameters": "^7.24.7" } }, "@babel/plugin-transform-object-super": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.1.tgz", - "integrity": "sha512-oKJqR3TeI5hSLRxudMjFQ9re9fBVUU0GICqM3J1mi8MqlhVr6hC/ZN4ttAyMuQR6EZZIY6h/exe5swqGNNIkWQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.24.7.tgz", + "integrity": "sha512-A/vVLwN6lBrMFmMDmPPz0jnE6ZGx7Jq7d6sT/Ev4H65RER6pZ+kczlf1DthF5N0qaPHBsI7UXiE8Zy66nmAovg==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-replace-supers": "^7.24.1" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-replace-supers": "^7.24.7" } }, "@babel/plugin-transform-optional-catch-binding": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.1.tgz", - "integrity": "sha512-oBTH7oURV4Y+3EUrf6cWn1OHio3qG/PVwO5J03iSJmBg6m2EhKjkAu/xuaXaYwWW9miYtvbWv4LNf0AmR43LUA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-catch-binding/-/plugin-transform-optional-catch-binding-7.24.7.tgz", + "integrity": "sha512-uLEndKqP5BfBbC/5jTwPxLh9kqPWWgzN/f8w6UwAIirAEqiIVJWWY312X72Eub09g5KF9+Zn7+hT7sDxmhRuKA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" } }, "@babel/plugin-transform-optional-chaining": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.5.tgz", - "integrity": "sha512-xWCkmwKT+ihmA6l7SSTpk8e4qQl/274iNbSKRRS8mpqFR32ksy36+a+LWY8OXCCEefF8WFlnOHVsaDI2231wBg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-optional-chaining/-/plugin-transform-optional-chaining-7.24.7.tgz", + "integrity": "sha512-tK+0N9yd4j+x/4hxF3F0e0fu/VdcxU18y5SevtyM/PCFlQvXbR0Zmlo2eBrKtVipGNFzpq56o8WsIIKcJFUCRQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7", "@babel/plugin-syntax-optional-chaining": "^7.8.3" } }, "@babel/plugin-transform-parameters": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.5.tgz", - "integrity": "sha512-9Co00MqZ2aoky+4j2jhofErthm6QVLKbpQrvz20c3CH9KQCLHyNB+t2ya4/UrRpQGR+Wrwjg9foopoeSdnHOkA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.24.7.tgz", + "integrity": "sha512-yGWW5Rr+sQOhK0Ot8hjDJuxU3XLRQGflvT4lhlSY0DFvdb3TwKaY26CJzHtYllU0vT9j58hc37ndFPsqT1SrzA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-private-methods": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.1.tgz", - "integrity": "sha512-tGvisebwBO5em4PaYNqt4fkw56K2VALsAbAakY0FjTYqJp7gfdrgr7YX76Or8/cpik0W6+tj3rZ0uHU9Oil4tw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-methods/-/plugin-transform-private-methods-7.24.7.tgz", + "integrity": "sha512-COTCOkG2hn4JKGEKBADkA8WNb35TGkkRbI5iT845dB+NyqgO8Hn+ajPbSnIQznneJTa3d30scb6iz/DhH8GsJQ==", "requires": { - "@babel/helper-create-class-features-plugin": "^7.24.1", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-private-property-in-object": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.5.tgz", - "integrity": "sha512-JM4MHZqnWR04jPMujQDTBVRnqxpLLpx2tkn7iPn+Hmsc0Gnb79yvRWOkvqFOx3Z7P7VxiRIR22c4eGSNj87OBQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-private-property-in-object/-/plugin-transform-private-property-in-object-7.24.7.tgz", + "integrity": "sha512-9z76mxwnwFxMyxZWEgdgECQglF2Q7cFLm0kMf8pGwt+GSJsY0cONKj/UuO4bOH0w/uAel3ekS4ra5CEAyJRmDA==", "requires": { - "@babel/helper-annotate-as-pure": "^7.22.5", - "@babel/helper-create-class-features-plugin": "^7.24.5", - "@babel/helper-plugin-utils": "^7.24.5", + "@babel/helper-annotate-as-pure": "^7.24.7", + "@babel/helper-create-class-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "@babel/plugin-syntax-private-property-in-object": "^7.14.5" } }, "@babel/plugin-transform-property-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.1.tgz", - "integrity": "sha512-LetvD7CrHmEx0G442gOomRr66d7q8HzzGGr4PMHGr+5YIm6++Yke+jxj246rpvsbyhJwCLxcTn6zW1P1BSenqA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.24.7.tgz", + "integrity": "sha512-EMi4MLQSHfd2nrCqQEWxFdha2gBCqU4ZcCng4WBGZ5CJL4bBRW0ptdqqDdeirGZcpALazVVNJqRmsO8/+oNCBA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-regenerator": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.1.tgz", - "integrity": "sha512-sJwZBCzIBE4t+5Q4IGLaaun5ExVMRY0lYwos/jNecjMrVCygCdph3IKv0tkP5Fc87e/1+bebAmEAGBfnRD+cnw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.24.7.tgz", + "integrity": "sha512-lq3fvXPdimDrlg6LWBoqj+r/DEWgONuwjuOuQCSYgRroXDH/IdM1C0IZf59fL5cHLpjEH/O6opIRBbqv7ELnuA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-plugin-utils": "^7.24.7", "regenerator-transform": "^0.15.2" } }, "@babel/plugin-transform-reserved-words": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.1.tgz", - "integrity": "sha512-JAclqStUfIwKN15HrsQADFgeZt+wexNQ0uLhuqvqAUFoqPMjEcFCYZBhq0LUdz6dZK/mD+rErhW71fbx8RYElg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.24.7.tgz", + "integrity": "sha512-0DUq0pHcPKbjFZCfTss/pGkYMfy3vFWydkUBd9r0GHpIyfs2eCDENvqadMycRS9wZCXR41wucAfJHJmwA0UmoQ==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-runtime": { - "version": "7.24.3", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.3.tgz", - "integrity": "sha512-J0BuRPNlNqlMTRJ72eVptpt9VcInbxO6iP3jaxr+1NPhC0UkKL+6oeX6VXMEYdADnuqmMmsBspt4d5w8Y/TCbQ==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.24.7.tgz", + "integrity": "sha512-YqXjrk4C+a1kZjewqt+Mmu2UuV1s07y8kqcUf4qYLnoqemhR4gRQikhdAhSVJioMjVTu6Mo6pAbaypEA3jY6fw==", "requires": { - "@babel/helper-module-imports": "^7.24.3", - "@babel/helper-plugin-utils": "^7.24.0", + "@babel/helper-module-imports": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.1", "babel-plugin-polyfill-regenerator": "^0.6.1", @@ -31335,102 +28970,102 @@ } }, "@babel/plugin-transform-shorthand-properties": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.1.tgz", - "integrity": "sha512-LyjVB1nsJ6gTTUKRjRWx9C1s9hE7dLfP/knKdrfeH9UPtAGjYGgxIbFfx7xyLIEWs7Xe1Gnf8EWiUqfjLhInZA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.24.7.tgz", + "integrity": "sha512-KsDsevZMDsigzbA09+vacnLpmPH4aWjcZjXdyFKGzpplxhbeB4wYtury3vglQkg6KM/xEPKt73eCjPPf1PgXBA==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-spread": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.1.tgz", - "integrity": "sha512-KjmcIM+fxgY+KxPVbjelJC6hrH1CgtPmTvdXAfn3/a9CnWGSTY7nH4zm5+cjmWJybdcPSsD0++QssDsjcpe47g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.24.7.tgz", + "integrity": "sha512-x96oO0I09dgMDxJaANcRyD4ellXFLLiWhuwDxKZX5g2rWP1bTPkBSwCYv96VDXVT1bD9aPj8tppr5ITIh8hBng==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0", - "@babel/helper-skip-transparent-expression-wrappers": "^7.22.5" + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.24.7" } }, "@babel/plugin-transform-sticky-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.1.tgz", - "integrity": "sha512-9v0f1bRXgPVcPrngOQvLXeGNNVLc8UjMVfebo9ka0WF3/7+aVUHmaJVT3sa0XCzEFioPfPHZiOcYG9qOsH63cw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.24.7.tgz", + "integrity": "sha512-kHPSIJc9v24zEml5geKg9Mjx5ULpfncj0wRpYtxbvKyTtHCYDkVE3aHQ03FrpEo4gEe2vrJJS1Y9CJTaThA52g==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-template-literals": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.1.tgz", - "integrity": "sha512-WRkhROsNzriarqECASCNu/nojeXCDTE/F2HmRgOzi7NGvyfYGq1NEjKBK3ckLfRgGc6/lPAqP0vDOSw3YtG34g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.24.7.tgz", + "integrity": "sha512-AfDTQmClklHCOLxtGoP7HkeMw56k1/bTQjwsfhL6pppo/M4TOBSq+jjBUBLmV/4oeFg4GWMavIl44ZeCtmmZTw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-typeof-symbol": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.5.tgz", - "integrity": "sha512-UTGnhYVZtTAjdwOTzT+sCyXmTn8AhaxOS/MjG9REclZ6ULHWF9KoCZur0HSGU7hk8PdBFKKbYe6+gqdXWz84Jg==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.24.7.tgz", + "integrity": "sha512-VtR8hDy7YLB7+Pet9IarXjg/zgCMSF+1mNS/EQEiEaUPoFXCVsHG64SIxcaaI2zJgRiv+YmgaQESUfWAdbjzgg==", "requires": { - "@babel/helper-plugin-utils": "^7.24.5" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-escapes": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.1.tgz", - "integrity": "sha512-RlkVIcWT4TLI96zM660S877E7beKlQw7Ig+wqkKBiWfj0zH5Q4h50q6er4wzZKRNSYpfo6ILJ+hrJAGSX2qcNw==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.24.7.tgz", + "integrity": "sha512-U3ap1gm5+4edc2Q/P+9VrBNhGkfnf+8ZqppY71Bo/pzZmXhhLdqgaUl6cuB07O1+AQJtCLfaOmswiNbSQ9ivhw==", "requires": { - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-property-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.1.tgz", - "integrity": "sha512-Ss4VvlfYV5huWApFsF8/Sq0oXnGO+jB+rijFEFugTd3cwSObUSnUi88djgR5528Csl0uKlrI331kRqe56Ov2Ng==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-property-regex/-/plugin-transform-unicode-property-regex-7.24.7.tgz", + "integrity": "sha512-uH2O4OV5M9FZYQrwc7NdVmMxQJOCCzFeYudlZSzUAHRFeOujQefa92E74TQDVskNHCzOXoigEuoyzHDhaEaK5w==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.1.tgz", - "integrity": "sha512-2A/94wgZgxfTsiLaQ2E36XAOdcZmGAaEEgVmxQWwZXWkGhvoHbaqXcKnU8zny4ycpu3vNqg0L/PcCiYtHtA13g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.24.7.tgz", + "integrity": "sha512-hlQ96MBZSAXUq7ltkjtu3FJCCSMx/j629ns3hA3pXnBXjanNP0LHi+JpPeA81zaWgVK1VGH95Xuy7u0RyQ8kMg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/plugin-transform-unicode-sets-regex": { - "version": "7.24.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.1.tgz", - "integrity": "sha512-fqj4WuzzS+ukpgerpAoOnMfQXwUHFxXUZUE84oL2Kao2N8uSlvcpnAidKASgsNgzZHBsHWvcm8s9FPWUhAb8fA==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-sets-regex/-/plugin-transform-unicode-sets-regex-7.24.7.tgz", + "integrity": "sha512-2G8aAvF4wy1w/AGZkemprdGMRg5o6zPNhbHVImRz3lss55TYCBd6xStN19rt8XJHq20sqV0JbyWjOWwQRwV/wg==", "requires": { - "@babel/helper-create-regexp-features-plugin": "^7.22.15", - "@babel/helper-plugin-utils": "^7.24.0" + "@babel/helper-create-regexp-features-plugin": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7" } }, "@babel/preset-env": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.5.tgz", - "integrity": "sha512-UGK2ifKtcC8i5AI4cH+sbLLuLc2ktYSFJgBAXorKAsHUZmrQ1q6aQ6i3BvU24wWs2AAKqQB6kq3N9V9Gw1HiMQ==", - "requires": { - "@babel/compat-data": "^7.24.4", - "@babel/helper-compilation-targets": "^7.23.6", - "@babel/helper-plugin-utils": "^7.24.5", - "@babel/helper-validator-option": "^7.23.5", - "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.5", - "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.1", - "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.1", - "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.1", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.24.7.tgz", + "integrity": "sha512-1YZNsc+y6cTvWlDHidMBsQZrZfEFjRIo/BZCT906PMdzOyXtSLTgqGdrpcuTDCXyd11Am5uQULtDIcCfnTc8fQ==", + "requires": { + "@babel/compat-data": "^7.24.7", + "@babel/helper-compilation-targets": "^7.24.7", + "@babel/helper-plugin-utils": "^7.24.7", + "@babel/helper-validator-option": "^7.24.7", + "@babel/plugin-bugfix-firefox-class-in-computed-class-key": "^7.24.7", + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression": "^7.24.7", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.24.7", + "@babel/plugin-bugfix-v8-static-class-fields-redefine-readonly": "^7.24.7", "@babel/plugin-proposal-private-property-in-object": "7.21.0-placeholder-for-preset-env.2", "@babel/plugin-syntax-async-generators": "^7.8.4", "@babel/plugin-syntax-class-properties": "^7.12.13", "@babel/plugin-syntax-class-static-block": "^7.14.5", "@babel/plugin-syntax-dynamic-import": "^7.8.3", "@babel/plugin-syntax-export-namespace-from": "^7.8.3", - "@babel/plugin-syntax-import-assertions": "^7.24.1", - "@babel/plugin-syntax-import-attributes": "^7.24.1", + "@babel/plugin-syntax-import-assertions": "^7.24.7", + "@babel/plugin-syntax-import-attributes": "^7.24.7", "@babel/plugin-syntax-import-meta": "^7.10.4", "@babel/plugin-syntax-json-strings": "^7.8.3", "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", @@ -31442,54 +29077,54 @@ "@babel/plugin-syntax-private-property-in-object": "^7.14.5", "@babel/plugin-syntax-top-level-await": "^7.14.5", "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6", - "@babel/plugin-transform-arrow-functions": "^7.24.1", - "@babel/plugin-transform-async-generator-functions": "^7.24.3", - "@babel/plugin-transform-async-to-generator": "^7.24.1", - "@babel/plugin-transform-block-scoped-functions": "^7.24.1", - "@babel/plugin-transform-block-scoping": "^7.24.5", - "@babel/plugin-transform-class-properties": "^7.24.1", - "@babel/plugin-transform-class-static-block": "^7.24.4", - "@babel/plugin-transform-classes": "^7.24.5", - "@babel/plugin-transform-computed-properties": "^7.24.1", - "@babel/plugin-transform-destructuring": "^7.24.5", - "@babel/plugin-transform-dotall-regex": "^7.24.1", - "@babel/plugin-transform-duplicate-keys": "^7.24.1", - "@babel/plugin-transform-dynamic-import": "^7.24.1", - "@babel/plugin-transform-exponentiation-operator": "^7.24.1", - "@babel/plugin-transform-export-namespace-from": "^7.24.1", - "@babel/plugin-transform-for-of": "^7.24.1", - "@babel/plugin-transform-function-name": "^7.24.1", - "@babel/plugin-transform-json-strings": "^7.24.1", - "@babel/plugin-transform-literals": "^7.24.1", - "@babel/plugin-transform-logical-assignment-operators": "^7.24.1", - "@babel/plugin-transform-member-expression-literals": "^7.24.1", - "@babel/plugin-transform-modules-amd": "^7.24.1", - "@babel/plugin-transform-modules-commonjs": "^7.24.1", - "@babel/plugin-transform-modules-systemjs": "^7.24.1", - "@babel/plugin-transform-modules-umd": "^7.24.1", - "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5", - "@babel/plugin-transform-new-target": "^7.24.1", - "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.1", - "@babel/plugin-transform-numeric-separator": "^7.24.1", - "@babel/plugin-transform-object-rest-spread": "^7.24.5", - "@babel/plugin-transform-object-super": "^7.24.1", - "@babel/plugin-transform-optional-catch-binding": "^7.24.1", - "@babel/plugin-transform-optional-chaining": "^7.24.5", - "@babel/plugin-transform-parameters": "^7.24.5", - "@babel/plugin-transform-private-methods": "^7.24.1", - "@babel/plugin-transform-private-property-in-object": "^7.24.5", - "@babel/plugin-transform-property-literals": "^7.24.1", - "@babel/plugin-transform-regenerator": "^7.24.1", - "@babel/plugin-transform-reserved-words": "^7.24.1", - "@babel/plugin-transform-shorthand-properties": "^7.24.1", - "@babel/plugin-transform-spread": "^7.24.1", - "@babel/plugin-transform-sticky-regex": "^7.24.1", - "@babel/plugin-transform-template-literals": "^7.24.1", - "@babel/plugin-transform-typeof-symbol": "^7.24.5", - "@babel/plugin-transform-unicode-escapes": "^7.24.1", - "@babel/plugin-transform-unicode-property-regex": "^7.24.1", - "@babel/plugin-transform-unicode-regex": "^7.24.1", - "@babel/plugin-transform-unicode-sets-regex": "^7.24.1", + "@babel/plugin-transform-arrow-functions": "^7.24.7", + "@babel/plugin-transform-async-generator-functions": "^7.24.7", + "@babel/plugin-transform-async-to-generator": "^7.24.7", + "@babel/plugin-transform-block-scoped-functions": "^7.24.7", + "@babel/plugin-transform-block-scoping": "^7.24.7", + "@babel/plugin-transform-class-properties": "^7.24.7", + "@babel/plugin-transform-class-static-block": "^7.24.7", + "@babel/plugin-transform-classes": "^7.24.7", + "@babel/plugin-transform-computed-properties": "^7.24.7", + "@babel/plugin-transform-destructuring": "^7.24.7", + "@babel/plugin-transform-dotall-regex": "^7.24.7", + "@babel/plugin-transform-duplicate-keys": "^7.24.7", + "@babel/plugin-transform-dynamic-import": "^7.24.7", + "@babel/plugin-transform-exponentiation-operator": "^7.24.7", + "@babel/plugin-transform-export-namespace-from": "^7.24.7", + "@babel/plugin-transform-for-of": "^7.24.7", + "@babel/plugin-transform-function-name": "^7.24.7", + "@babel/plugin-transform-json-strings": "^7.24.7", + "@babel/plugin-transform-literals": "^7.24.7", + "@babel/plugin-transform-logical-assignment-operators": "^7.24.7", + "@babel/plugin-transform-member-expression-literals": "^7.24.7", + "@babel/plugin-transform-modules-amd": "^7.24.7", + "@babel/plugin-transform-modules-commonjs": "^7.24.7", + "@babel/plugin-transform-modules-systemjs": "^7.24.7", + "@babel/plugin-transform-modules-umd": "^7.24.7", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.24.7", + "@babel/plugin-transform-new-target": "^7.24.7", + "@babel/plugin-transform-nullish-coalescing-operator": "^7.24.7", + "@babel/plugin-transform-numeric-separator": "^7.24.7", + "@babel/plugin-transform-object-rest-spread": "^7.24.7", + "@babel/plugin-transform-object-super": "^7.24.7", + "@babel/plugin-transform-optional-catch-binding": "^7.24.7", + "@babel/plugin-transform-optional-chaining": "^7.24.7", + "@babel/plugin-transform-parameters": "^7.24.7", + "@babel/plugin-transform-private-methods": "^7.24.7", + "@babel/plugin-transform-private-property-in-object": "^7.24.7", + "@babel/plugin-transform-property-literals": "^7.24.7", + "@babel/plugin-transform-regenerator": "^7.24.7", + "@babel/plugin-transform-reserved-words": "^7.24.7", + "@babel/plugin-transform-shorthand-properties": "^7.24.7", + "@babel/plugin-transform-spread": "^7.24.7", + "@babel/plugin-transform-sticky-regex": "^7.24.7", + "@babel/plugin-transform-template-literals": "^7.24.7", + "@babel/plugin-transform-typeof-symbol": "^7.24.7", + "@babel/plugin-transform-unicode-escapes": "^7.24.7", + "@babel/plugin-transform-unicode-property-regex": "^7.24.7", + "@babel/plugin-transform-unicode-regex": "^7.24.7", + "@babel/plugin-transform-unicode-sets-regex": "^7.24.7", "@babel/preset-modules": "0.1.6-no-external-plugins", "babel-plugin-polyfill-corejs2": "^0.4.10", "babel-plugin-polyfill-corejs3": "^0.10.4", @@ -31508,56 +29143,170 @@ "esutils": "^2.0.2" } }, + "@babel/register": { + "version": "7.24.6", + "resolved": "https://registry.npmjs.org/@babel/register/-/register-7.24.6.tgz", + "integrity": "sha512-WSuFCc2wCqMeXkz/i3yfAAsxwWflEgbVkZzivgAmXl/MxrXeoYFZOOPllbC8R8WTF7u61wSRQtDVZ1879cdu6w==", + "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 + }, + "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" + } + } + } + }, "@babel/regjsgen": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@babel/regjsgen/-/regjsgen-0.8.0.tgz", "integrity": "sha512-x/rqGMdzj+fWZvCOYForTghzbtqPDZ5gPwaoNGHdgDfF2QA/XZbCBp4Moo5scrkAMPhB7z26XM/AaHuIJdgauA==" }, "@babel/runtime": { - "version": "7.24.5", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.5.tgz", - "integrity": "sha512-Nms86NXrsaeU9vbBJKni6gXiEXZ4CVpYVzEjDH9Sb8vmZ3UljyA1GSOJl/6LGPO8EHLuSF9H+IxNXHPX8QHJ4g==", + "version": "7.24.7", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.24.7.tgz", + "integrity": "sha512-UwgBRMjJP+xv857DCngvqXI3Iq6J4v0wXmwc6sapg+zyhbwmQX67LUEFrkK5tbyJ30jGuG3ZvWpBiB9LCy1kWw==", "requires": { "regenerator-runtime": "^0.14.0" } }, "@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.0", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.0.tgz", + "integrity": "sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q==", "requires": { - "@babel/code-frame": "^7.23.5", - "@babel/parser": "^7.24.0", - "@babel/types": "^7.24.0" + "@babel/code-frame": "^7.24.7", + "@babel/parser": "^7.25.0", + "@babel/types": "^7.25.0" } }, "@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.25.6", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.25.6.tgz", + "integrity": "sha512-9Vrcx5ZW6UwK5tvqsj0nGpp/XzqthkT0dqIc9g1AdtygFToNtTF67XzYS//dm+SAK9cp3B9R4ZO/46p63SCjlQ==", + "requires": { + "@babel/code-frame": "^7.24.7", + "@babel/generator": "^7.25.6", + "@babel/parser": "^7.25.6", + "@babel/template": "^7.25.0", + "@babel/types": "^7.25.6", "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.25.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.6.tgz", + "integrity": "sha512-/l42B1qxpG6RdfYf343Uw1vmDjeNhneUXtzhojE7pDgfpEypmRhI6j1kr17XCVv4Cgl9HdAiQY2x0GwKm7rWCw==", "requires": { - "@babel/helper-string-parser": "^7.24.1", - "@babel/helper-validator-identifier": "^7.24.5", + "@babel/helper-string-parser": "^7.24.8", + "@babel/helper-validator-identifier": "^7.24.7", "to-fast-properties": "^2.0.0" } }, + "@browserstack/ai-sdk-node": { + "version": "1.5.8", + "resolved": "https://registry.npmjs.org/@browserstack/ai-sdk-node/-/ai-sdk-node-1.5.8.tgz", + "integrity": "sha512-34snogSnvskHxUZYOX61ga1/oTlyXwneRtd7Epu2bEdSsRR1rMm8xXhO6DVrLsHPwPHz+ljAlwVwhcE2uKysxw==", + "dev": true, + "requires": { + "axios": "^1.6.2", + "uuid": "9.0.1" + } + }, "@colors/colors": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", @@ -31571,16 +29320,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 +29678,210 @@ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", "dev": true }, + "@inquirer/checkbox": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-2.4.7.tgz", + "integrity": "sha512-5YwCySyV1UEgqzz34gNsC38eKxRBtlRDpJLlKcRtTjlYA/yDKuc1rfw+hjw+2WJxbAZtaDPsRl5Zk7J14SBoBw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/confirm": { + "version": "3.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-3.1.22.tgz", + "integrity": "sha512-gsAKIOWBm2Q87CDfs9fEo7wJT3fwWIJfnDGMn9Qy74gBnNFOACDNfhUzovubbJjWnKLGBln7/NcSmZwj5DuEXg==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/core": { + "version": "9.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-9.0.10.tgz", + "integrity": "sha512-TdESOKSVwf6+YWDz8GhS6nKscwzkIyakEzCLJ5Vh6O3Co2ClhCJ0A4MG909MUWfaWdpJm7DE45ii51/2Kat9tA==", + "dev": true, + "requires": { + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "@types/mute-stream": "^0.0.4", + "@types/node": "^22.1.0", + "@types/wrap-ansi": "^3.0.0", + "ansi-escapes": "^4.3.2", + "cli-spinners": "^2.9.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.4.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.4.1.tgz", + "integrity": "sha512-1tbpb9325+gPnKK0dMm+/LMriX0vKxf6RnB0SZUqfyVkQ4fMgUSySqhxE/y8Jvs4NyF1yHzTfG9KlnkIODxPKg==", + "dev": true, + "requires": { + "undici-types": "~6.19.2" + } + }, + "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 + }, + "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.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", + "dev": true + } + } + }, + "@inquirer/editor": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-2.1.22.tgz", + "integrity": "sha512-K1QwTu7GCK+nKOVRBp5HY9jt3DXOfPGPr6WRDrPImkcJRelG9UTx2cAtK1liXmibRrzJlTWOwqgWT3k2XnS62w==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "external-editor": "^3.1.0" + } + }, + "@inquirer/expand": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-2.1.22.tgz", + "integrity": "sha512-wTZOBkzH+ItPuZ3ZPa9lynBsdMp6kQ9zbjVPYEtSBG7UulGjg2kQiAnUjgyG4SlntpTce5bOmXAPvE4sguXjpA==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/figures": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.5.tgz", + "integrity": "sha512-79hP/VWdZ2UVc9bFGJnoQ/lQMpL74mGgzSYX1xUqCVk7/v73vJCMw1VuyWN1jGkZ9B3z7THAbySqGbCNefcjfA==", + "dev": true + }, + "@inquirer/input": { + "version": "2.2.9", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-2.2.9.tgz", + "integrity": "sha512-7Z6N+uzkWM7+xsE+3rJdhdG/+mQgejOVqspoW+w0AbSZnL6nq5tGMEVASaYVWbkoSzecABWwmludO2evU3d31g==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/number": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-1.0.10.tgz", + "integrity": "sha512-kWTxRF8zHjQOn2TJs+XttLioBih6bdc5CcosXIzZsrTY383PXI35DuhIllZKu7CdXFi2rz2BWPN9l0dPsvrQOA==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2" + } + }, + "@inquirer/password": { + "version": "2.1.22", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-2.1.22.tgz", + "integrity": "sha512-5Fxt1L9vh3rAKqjYwqsjU4DZsEvY/2Gll+QkqR4yEpy6wvzLxdSgFhUcxfDAOtO4BEoTreWoznC0phagwLU5Kw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2" + } + }, + "@inquirer/prompts": { + "version": "5.3.8", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-5.3.8.tgz", + "integrity": "sha512-b2BudQY/Si4Y2a0PdZZL6BeJtl8llgeZa7U2j47aaJSCeAl1e4UI7y8a9bSkO3o/ZbZrgT5muy/34JbsjfIWxA==", + "dev": true, + "requires": { + "@inquirer/checkbox": "^2.4.7", + "@inquirer/confirm": "^3.1.22", + "@inquirer/editor": "^2.1.22", + "@inquirer/expand": "^2.1.22", + "@inquirer/input": "^2.2.9", + "@inquirer/number": "^1.0.10", + "@inquirer/password": "^2.1.22", + "@inquirer/rawlist": "^2.2.4", + "@inquirer/search": "^1.0.7", + "@inquirer/select": "^2.4.7" + } + }, + "@inquirer/rawlist": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-2.2.4.tgz", + "integrity": "sha512-pb6w9pWrm7EfnYDgQObOurh2d2YH07+eDo3xQBsNAM2GRhliz6wFXGi1thKQ4bN6B0xDd6C3tBsjdr3obsCl3Q==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/search": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-1.0.7.tgz", + "integrity": "sha512-p1wpV+3gd1eST/o5N3yQpYEdFNCzSP0Klrl+5bfD3cTTz8BGG6nf4Z07aBW0xjlKIj1Rp0y3x/X4cZYi6TfcLw==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/select": { + "version": "2.4.7", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-2.4.7.tgz", + "integrity": "sha512-JH7XqPEkBpNWp3gPCqWqY8ECbyMoFcCZANlL6pV9hf59qK6dGmkOlx1ydyhY+KZ0c5X74+W6Mtp+nm2QX0/MAQ==", + "dev": true, + "requires": { + "@inquirer/core": "^9.0.10", + "@inquirer/figures": "^1.0.5", + "@inquirer/type": "^1.5.2", + "ansi-escapes": "^4.3.2", + "yoctocolors-cjs": "^2.1.2" + } + }, + "@inquirer/type": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-1.5.2.tgz", + "integrity": "sha512-w9qFkumYDCNyDZmNQjf/n6qQuvQ4dMC3BJesY4oF+yr0CxR5vxujflAVeIcS6U336uzi9GM0kAfZlLrZ9UTkpA==", + "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", @@ -31948,19 +30072,10 @@ "@jridgewell/trace-mapping": { "version": "0.3.25", "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", - "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", - "requires": { - "@jridgewell/resolve-uri": "^3.1.0", - "@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, + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", "requires": { - "call-bind": "^1.0.7" + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" } }, "@nicolo-ribaudo/eslint-scope-5-internals": { @@ -31989,9 +30104,9 @@ } }, "@percy/sdk-utils": { - "version": "1.28.6", - "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.6.tgz", - "integrity": "sha512-cfpylC1bvp93EPQpwvYybdw5I4/fAABKR0hfCcyN7LF0FRgXE3/Onj7ebW41gypDilw2Q1nW0gUSwPj9MyoTOg==", + "version": "1.28.7", + "resolved": "https://registry.npmjs.org/@percy/sdk-utils/-/sdk-utils-1.28.7.tgz", + "integrity": "sha512-LIhfHnkcS0fyIdo3gvKn7rwodZjbEtyLkgiDRSRulcBOatI2mhn2Bh269sXXiiFTyAW2BDQjyE3DWc4hkGbsbQ==", "dev": true }, "@percy/selenium-webdriver": { @@ -32011,6 +30126,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 +30164,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 +30190,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 +30244,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 +30255,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", @@ -32188,16 +30295,6 @@ "@types/json-schema": "*" } }, - "@types/eslint-scope": { - "version": "3.7.7", - "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.7.tgz", - "integrity": "sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==", - "dev": true, - "requires": { - "@types/eslint": "*", - "@types/estree": "*" - } - }, "@types/estree": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", @@ -32222,16 +30319,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 +30328,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 +30364,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 +30373,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,10 +30385,19 @@ "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", - "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", + "version": "20.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.2.tgz", + "integrity": "sha512-xyu6WAMVwv6AKFLB+e/7ySZVr/0zLCzOa7rSpq6jNwpqOrUbcACDWC+53d4n2QHOnDou0fbIsg8wZu/sxrnI4Q==", "dev": true, "requires": { "undici-types": "~5.26.4" @@ -32340,14 +30415,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 +30439,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 +30461,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.12", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", "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,10 +30501,16 @@ "@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", - "integrity": "sha512-etPTUdCFu7gUWc+1XcbiPr+lrhOcBu3rV5OL1M+3PDW89zskScAkkcdqYzP4pFodBPye/ydamQoTDScOnElw5A==", + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/@videojs/http-streaming/-/http-streaming-2.16.3.tgz", + "integrity": "sha512-91CJv5PnFBzNBvyEjt+9cPzTK/xoVixARj2g7ZAvItA+5bx8VKdk5RxCz/PP2kdzz9W+NiDUMPkdmTsosmy69Q==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", @@ -32467,6 +30545,15 @@ "is-function": "^1.0.1" } }, + "@vitest/pretty-format": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.0.5.tgz", + "integrity": "sha512-h8k+1oWHfwTkyTkb9egzwNMfJAEx4veaPSnMeKbVSjp4euqGSbQlm5+6VHwTr7u4FJslVVsUG5nopCaAYdOmSQ==", + "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", @@ -32540,89 +30627,109 @@ "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.0.5", + "resolved": "https://registry.npmjs.org/@wdio/browserstack-service/-/browserstack-service-9.0.5.tgz", + "integrity": "sha512-pJNb9jJwPf+FEwAEnnUc6d9s6/QlvcZbh9NtjO23a/wr3HvXdzhlRHwzUV1RWboDpGsww5PFmtGcIo7GdDQL+g==", "dev": true, "requires": { + "@browserstack/ai-sdk-node": "1.5.8", "@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.0.4", + "@wdio/reporter": "9.0.4", + "@wdio/types": "9.0.4", "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.0.5", "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==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "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" + "debug": "^4.3.5", + "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" } }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "dev": true, "requires": { - "debug": "^4.3.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "archiver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz", - "integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==", + "@wdio/reporter": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-9.0.4.tgz", + "integrity": "sha512-g55MiqToKEZ+L5quk7Ddk3m1fKgh2U2rq3zLG8bZUYU+3KFglfRC/Ld5yTso8S1tG4EDgl5HKSN5Bl8I5gncbg==", "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" + "@types/node": "^20.1.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "diff": "^5.0.0", + "object-inspect": "^1.12.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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "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 + "@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" + } + }, + "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" + } }, "brace-expansion": { "version": "2.0.1", @@ -32633,298 +30740,36 @@ "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" - } - }, - "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==", + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "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==", + "devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "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", @@ -32932,157 +30777,117 @@ "dev": true }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "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" + "socks-proxy-agent": "^8.0.2" } }, "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==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, + "optional": true, + "peer": true, "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.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" - } + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "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==", + "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": { - "safe-buffer": "~5.2.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "type-fest": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz", - "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==", + "uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", "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", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": 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", @@ -33093,449 +30898,172 @@ "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.0.5", + "resolved": "https://registry.npmjs.org/@wdio/cli/-/cli-9.0.5.tgz", + "integrity": "sha512-D/QBlodNIdxuNpUPbuhk+mLidVLT+Vsb0Q0Fd4lh57Jy8kw5nJ56ykqiI0WE1oI0i+XtyJ7iFOPUztuCjjhX3A==", "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", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "async-exit-hook": "^2.0.1", "chalk": "^5.2.0", "chokidar": "^3.5.3", - "cli-spinners": "^2.9.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": "^10.1.8", "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", + "tsx": "^4.7.2", + "webdriverio": "9.0.5", "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", "dev": true, "requires": { - "node-fetch": "^2.6.12" + "debug": "^4.3.6", + "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": { - "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" - } - } + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "dev": true, + "requires": { + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.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==", + "@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", + "dev": true, + "requires": { + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" + } + }, + "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" + } + }, + "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 }, - "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==", + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true + }, + "devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "dev": true, "optional": true, "peer": true }, "execa": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", - "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "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": { + "@sindresorhus/merge-streams": "^4.0.0", "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", + "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": "^3.0.0" + "strip-final-newline": "^4.0.0", + "yoctocolors": "^2.0.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 + "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": { + "@sec-ant/readable-stream": "^0.4.1", + "is-stream": "^4.0.1" + } }, "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", @@ -33543,157 +31071,113 @@ } }, "is-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", - "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "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 }, - "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 }, - "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 - }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "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" - } - }, "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, - "requires": { - "path-key": "^4.0.0" - } - }, - "onetime": { "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" + "socks-proxy-agent": "^8.0.2" } }, "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==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, + "optional": true, + "peer": 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" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.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 + "@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "^4.3.5", + "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" + } } } }, - "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" - } + "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,66 +31185,60 @@ "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==", + "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": { - "safe-buffer": "~5.2.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "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==", - "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", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": true, "requires": {} }, "yargs": { @@ -33777,28 +31255,17 @@ "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/concise-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==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/concise-reporter/-/concise-reporter-8.38.2.tgz", + "integrity": "sha512-wE36By4Z9iCtRzihpYrmCehsmNc8t3gHviBsUxV4tmYh/SQr+WX/dysWnojer6KWIJ2rT0rOzyQPmrwhdFKAFg==", "dev": true, "requires": { - "@wdio/reporter": "8.36.1", - "@wdio/types": "8.36.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.0.1", "pretty-ms": "^7.0.1" }, @@ -33812,44 +31279,76 @@ } }, "@wdio/config": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/config/-/config-8.36.1.tgz", - "integrity": "sha512-yCENnym0CrYuLKMJ3fv00WkjCR8QpPqVohGBkq5FvZOZpVJEpoG86Q8l4HtyRnd6ggMTKCA1vTQ/myhbPmZmaQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.5.tgz", + "integrity": "sha512-+dxUU2SLXLkqQhVU/wauU1VgqEKIFubOyUb6B0ueAMpM1aolc62zhE9D9rrQYbjkPOM7nFsjuuGR5+9+zaoZ6g==", "dev": true, "requires": { - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", - "@wdio/utils": "8.36.1", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", + "dev": true, + "requires": { + "debug": "^4.3.6", + "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": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "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" + "@types/node": "^20.1.0" + } + }, + "@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" } }, "agent-base": { @@ -33857,352 +31356,237 @@ "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==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true + }, + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true + }, + "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, - "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" + "agent-base": "^7.0.2", + "debug": "4" } }, - "archiver-utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz", - "integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==", + "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 + }, + "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, - "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" + "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" } }, - "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 + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "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==", + "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": { - "balanced-match": "^1.0.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "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": { + "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/globals": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/globals/-/globals-9.0.5.tgz", + "integrity": "sha512-ZkopKj1qEDNKuF1a87JTLfTKCBFgCHLUns5ob5D1oEmMFp0NwB89HHGBWgtuJpCUmxJAbf4rCKglVeKhB9rY7A==", + "dev": true, + "requires": { + "expect-webdriverio": "^5.0.1", + "webdriverio": "9.0.5" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", "dev": true, "optional": true, "requires": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" + "debug": "^4.3.5", + "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" } }, - "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==", + "@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, - "optional": true + "optional": true, + "requires": { + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.2" + } }, - "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==", + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "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" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "compress-commons": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz", - "integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==", + "@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "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" + "@types/node": "^20.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/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", "dev": true, "optional": true, "requires": { - "crc-32": "^1.2.0", - "readable-stream": "^4.0.0" + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" } }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "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": { - "node-fetch": "^2.6.12" + "debug": "^4.3.4" } }, - "devtools": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/devtools/-/devtools-8.36.1.tgz", - "integrity": "sha512-7MGd3ZF8UIUqmxn8swqmGVGEOWi2DB9dmqFFJtvE2v6+I2RdvyRc2aSxYjK3j1I7u3BMlvS33wulVofSAkQz0A==", + "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, - "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" - } - } + "balanced-match": "^1.0.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==", + "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 }, - "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==", + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true, + "optional": true + }, + "devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "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==", + "expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, "optional": true, "requires": { - "agent-base": "^7.0.2", - "debug": "4" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" } }, - "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==", + "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, "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" - } - } + "agent-base": "^7.0.2", + "debug": "4" } }, "lru-cache": { @@ -34213,168 +31597,116 @@ "optional": true }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "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==", + "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" + "socks-proxy-agent": "^8.0.2" } }, "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==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, "optional": true, + "peer": true, "requires": { - "abort-controller": "^3.0.0", - "buffer": "^6.0.3", - "events": "^3.3.0", - "process": "^0.11.10", - "string_decoder": "^1.3.0" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.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==", + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", "dev": true, - "optional": true, - "requires": { - "type-fest": "^2.12.2" - } + "optional": 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==", + "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": { - "safe-buffer": "~5.2.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "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==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", "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", + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, "optional": true, + "peer": 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": { @@ -34386,41 +31718,58 @@ "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" - } } } }, "@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.0.5", + "resolved": "https://registry.npmjs.org/@wdio/local-runner/-/local-runner-9.0.5.tgz", + "integrity": "sha512-BFZ/e7z1s2cYsix1evijydaDn0YffeIHjPsMoa9b+zhW8BoZfTEDGKblYvzRgjUDD4elXs+YRZpA6EhjcGJTxQ==", "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.0.4", + "@wdio/repl": "9.0.4", + "@wdio/runner": "9.0.5", + "@wdio/types": "9.0.4", "async-exit-hook": "^2.0.1", "split2": "^4.1.0", "stream-buffers": "^3.0.2" + }, + "dependencies": { + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", + "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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", + "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", @@ -34438,125 +31787,143 @@ } }, "@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==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/mocha-framework/-/mocha-framework-8.38.2.tgz", + "integrity": "sha512-qJmRL5E6/ypjCUACH4hvCAAmTdU4YUrUlp9o/IKvTIAHMnZPE0/HgUFixCeu8Mop+rdzTPVBrbqxpRDdSnraYA==", "dev": true, "requires": { "@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", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", + "@wdio/utils": "8.38.2", "mocha": "^10.0.0" } }, "@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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.4.tgz", + "integrity": "sha512-T9v8Jkp94NxLLil5J7uJ/+YHk5/7fhOggzGcN+LvuCHS6jbJFZ/11c4SUEuXw27Yqk01fFXPBbF6TwcTTOuW/Q==", "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.4", + "resolved": "https://registry.npmjs.org/@wdio/repl/-/repl-9.0.4.tgz", + "integrity": "sha512-5Bc5ARjWA7t6MZNnVJ9WvO1iDsy6iOsrSDWiP7APWAdaF/SJCP3SFE2c+PdQJpJWhr2Kk0fRGuyDM+GdsmZhwg==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/reporter": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.36.1.tgz", - "integrity": "sha512-HcXr9XKq/6kPC9nexMRXIc/ft3Lvp0yCaW5tps01Axus9wbi5ysLHi2z5sB84F2YdpM+aRf7Lac56xkc4Jldeg==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/reporter/-/reporter-8.38.2.tgz", + "integrity": "sha512-R78UdAtAnkaV22NYlCCcbPPhmYweiDURiw64LYhlVIQrKNuXUQcafR2kRlWKy31rZc9thSLs5LzrZDReENUlFQ==", "dev": true, "requires": { "@types/node": "^20.1.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "diff": "^5.0.0", "object-inspect": "^1.12.0" } }, "@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.0.5", + "resolved": "https://registry.npmjs.org/@wdio/runner/-/runner-9.0.5.tgz", + "integrity": "sha512-qZF7k3BeQaM7pQRwIvedbfaC7xBU1xRY+wFkp44U/wvYZOOrqWiwv/Synk1iCFkOdxl/b+Gqp68dDmS9BrVDmw==", "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", + "@wdio/config": "9.0.5", + "@wdio/globals": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", + "expect-webdriverio": "^5.0.1", "gaze": "^1.1.3", - "webdriver": "8.36.1", - "webdriverio": "8.36.1" + "webdriver": "9.0.5", + "webdriverio": "9.0.5" }, "dependencies": { "@puppeteer/browsers": { - "version": "1.4.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-1.4.6.tgz", - "integrity": "sha512-x4BEjr2SjOPowNeiguzjozQbsc6h437ovD/wu+JpaenxVLm3jkgzHY2xOslMTp50HoTvQreMjiexiGQw1sqZlQ==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", "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" + "debug": "^4.3.6", + "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" } }, - "agent-base": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz", - "integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==", + "@vitest/snapshot": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.0.5.tgz", + "integrity": "sha512-SgCPUeDFLaM0mIUHfaArq8fD2WbaXG/zVXjRupthYfYGzc8ztbFbu6dUNOblBG7XLMR1kEhS/DNnfCZ2IhdDew==", "dev": true, "requires": { - "debug": "^4.3.4" + "@vitest/pretty-format": "2.0.5", + "magic-string": "^0.30.10", + "pathe": "^1.1.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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "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.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "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 + "@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" + } + }, + "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" + } }, "brace-expansion": { "version": "2.0.1", @@ -34567,292 +31934,48 @@ "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" - } + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true }, - "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==", + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", "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==", + "devtools-protocol": { + "version": "0.0.1312386", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1312386.tgz", + "integrity": "sha512-DPnhUXvmvKT2dFA/j7B+riVLUt9Q6RKJlcppojL5CoRywJJKLDYnRlw0gTFKfgDPHP5E04UoB71SxoJlVZy8FA==", "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" - } + "peer": true }, - "cross-fetch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.0.0.tgz", - "integrity": "sha512-e4a5N8lVvuLgAWgnCrLr2PP0YyDOTHa9H/Rj54dirp61qXnNq46m82bRhNqIA5VccJtWBvPTFRV3TtvHUKPB1g==", + "expect-webdriverio": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/expect-webdriverio/-/expect-webdriverio-5.0.1.tgz", + "integrity": "sha512-tYJaXtu5qQr/oEXzDKoVzyA/g2wyeE1YsuVJQC8/bKnp1lX3I4NQ2Yb9u1rJ9vdpFha6pki8PzHlTk5PVZBQWQ==", "dev": true, "requires": { - "node-fetch": "^2.6.12" + "@vitest/snapshot": "^2.0.5", + "expect": "^29.7.0", + "jest-matcher-utils": "^29.7.0", + "lodash.isequal": "^4.5.0" } }, - "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==", + "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", @@ -34860,157 +31983,131 @@ "dev": true }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "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" + "socks-proxy-agent": "^8.0.2" } }, "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==", + "version": "22.15.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-22.15.0.tgz", + "integrity": "sha512-cHArnywCiAAVXa3t4GGL2vttNxh7GqXtIYGym99egkNJ3oG//wL9LkvO4WE8W1TJe95t1F1ocu9X4xWaGsOKOA==", "dev": true, + "optional": true, + "peer": 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" + "@puppeteer/browsers": "2.3.0", + "chromium-bidi": "0.6.3", + "debug": "^4.3.6", + "devtools-protocol": "0.0.1312386", + "ws": "^8.18.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 + "@puppeteer/browsers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.0.tgz", + "integrity": "sha512-ioXoq9gPxkss4MYhD+SFaU9p1IHFUX0ILAWFPyjGaBdjLsYAlZw6j1iLA0N/m12uVHLFDfSYNF7EQccjinIMDA==", + "dev": true, + "optional": true, + "peer": true, + "requires": { + "debug": "^4.3.5", + "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" + } } } }, - "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" - } + "semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "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==", + "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": { - "safe-buffer": "~5.2.0" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "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==", - "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", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.5.tgz", + "integrity": "sha512-80zhuLBT5W5wiLNZ0maT1cVUrxmwpMuBgCprwZjI8lFe+KUhGLClrJXud/RrVT9x9rDCYa6pGCtQ4UqA+2U+sw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/repl": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "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", - "devtools-protocol": "^0.0.1282316", - "grapheme-splitter": "^1.0.2", + "grapheme-splitter": "^1.0.4", + "htmlfy": "^0.2.1", "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.0", - "puppeteer-core": "^20.9.0", - "query-selector-shadow-dom": "^1.0.0", - "resq": "^1.9.1", + "minimatch": "^9.0.3", + "query-selector-shadow-dom": "^1.0.1", + "resq": "^1.11.0", "rgb2hex": "0.2.5", - "serialize-error": "^11.0.1", - "webdriver": "8.36.1" + "serialize-error": "^11.0.3", + "urlpattern-polyfill": "^10.0.0", + "webdriver": "9.0.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==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, + "optional": true, + "peer": 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,28 +32118,17 @@ "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/spec-reporter": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.36.1.tgz", - "integrity": "sha512-VgAd8VQCfwKYz4A3BPDUYNIQxXhRSTaVNbmDzSlYfo5Jekygk7fz0LRFYBpJ69l7eQH0P5nzEyF92oW/rvE3VA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/spec-reporter/-/spec-reporter-8.38.2.tgz", + "integrity": "sha512-Dntk+lmrp+0I3HRRWkkXED+smshvgsW5cdLKwJhEJ1liI48MdBpdNGf9IHTVckE6nfxcWDyFI4icD9qYv/5bFA==", "dev": true, "requires": { - "@wdio/reporter": "8.36.1", - "@wdio/types": "8.36.1", + "@wdio/reporter": "8.38.2", + "@wdio/types": "8.38.2", "chalk": "^5.1.2", "easy-table": "^1.2.0", "pretty-ms": "^7.0.0" @@ -35057,26 +32143,26 @@ } }, "@wdio/types": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.36.1.tgz", - "integrity": "sha512-kKtyJbypasKo/VQuJ6dTQQwFtHE9qoygjoCZjrQCLGraRSjOEiqZHPR0497wbeCvcgHIYyImbmcylqZNGUE0CQ==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-8.38.2.tgz", + "integrity": "sha512-+wj1c1OSLdnN4WO5b44Ih4263dTl/eSwMGSI4/pCgIyXIuYQH38JQ+6WRa+c8vJEskUzboq2cSgEQumVZ39ozQ==", "dev": true, "requires": { "@types/node": "^20.1.0" } }, "@wdio/utils": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.36.1.tgz", - "integrity": "sha512-xmgPHU11/o9n2FeRmDFkPRC0okiwA1i2xOcR2c3aSpuk99XkAm9RaMn/6u9LFaqsCpgaVxazcYEGSceO7U4hZA==", + "version": "8.38.2", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-8.38.2.tgz", + "integrity": "sha512-y5AnBwsGcu/XuCBGCgKmlvKdwEIFyzLA+Cr+denySxY3jbWDONtPUcGaVdFALwsIa5jcIjcATqGmZcCPGnkd7g==", "dev": true, "requires": { "@puppeteer/browsers": "^1.6.0", - "@wdio/logger": "8.28.0", - "@wdio/types": "8.36.1", + "@wdio/logger": "8.38.0", + "@wdio/types": "8.38.2", "decamelize": "^6.0.0", "deepmerge-ts": "^5.1.0", - "edgedriver": "^5.3.5", + "edgedriver": "^5.5.0", "geckodriver": "^4.3.1", "get-port": "^7.0.0", "import-meta-resolve": "^4.0.0", @@ -35250,6 +32336,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", @@ -35421,82 +32513,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 +32634,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 +32802,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 +32927,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 } } @@ -35866,165 +33007,40 @@ "dev": true }, "aws4": { - "version": "1.12.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", - "integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.0.tgz", + "integrity": "sha512-3AungXC4I8kKsS9PuS4JH2nc+0bVY/mjgrephHTIi8fpEeGsTHBUJeosp0Wc1myYMElmD0B3Oc4XL/HVJ4PV2g==", "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==", + "axios": { + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "follow-redirects": "^1.15.6", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" }, "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==", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.1", - "debug": "^2.6.9", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.8", - "slash": "^1.0.0", - "source-map": "^0.5.7" - }, - "dependencies": { - "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.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", "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 +33054,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 +33094,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", @@ -36243,22 +33124,22 @@ "dev": true }, "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==", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.4.2.tgz", + "integrity": "sha512-qMKFd2qG/36aA4GwvKq8MxnPgCQAmBWmSyLWsJcbn8v03wvIPQ/hG1Ms8bPzndZxMDoHpxez5VOS+gC9Yi24/Q==", "dev": true, "optional": true }, "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==", + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-2.3.1.tgz", + "integrity": "sha512-W/Hfxc/6VehXlsgFtbB5B4xFcsCl+pAh30cYhoFyXErf6oGrwjh8SwiPAdHgpmWonKuYpZgGywN0SXt7dgsADA==", "dev": true, "optional": true, "requires": { "bare-events": "^2.0.0", "bare-path": "^2.0.0", - "bare-stream": "^1.0.0" + "bare-stream": "^2.0.0" } }, "bare-os": { @@ -36269,9 +33150,9 @@ "optional": true }, "bare-path": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.2.tgz", - "integrity": "sha512-o7KSt4prEphWUHa3QUwCxUI00R86VdjiuxmJK0iNVDHYPGo+HsDaVCnqCmPbf/MiW1ok8F4p3m8RTHlWk8K2ig==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-2.1.3.tgz", + "integrity": "sha512-lh/eITfU8hrj9Ru5quUp0Io1kJWIk1bTjzo7JH1P5dWmQ2EL4hFUlfI8FonAhSlgIfhn63p84CDY/x+PisgcXA==", "dev": true, "optional": true, "requires": { @@ -36279,13 +33160,13 @@ } }, "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==", + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.1.3.tgz", + "integrity": "sha512-tiDAH9H/kP+tvNO5sczyn9ZAA7utrSMobyDchsnyyXBuUe2FSQWbxhtuHB8jwpHYYevVo2UJpcmvvjrbHboUUQ==", "dev": true, "optional": true, "requires": { - "streamx": "^2.16.1" + "streamx": "^2.18.0" } }, "base": { @@ -36414,30 +33295,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 +33337,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 +33349,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 +33387,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 +33402,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.23.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.23.1.tgz", + "integrity": "sha512-TUfofFo/KsK/bWZ9TWQ5O26tsWW4Uhmt8IYklbnUa70udB6P2wA7w7o4PY4muaEPBQaAX+CEnmmIA41NVHtPVw==", "requires": { - "caniuse-lite": "^1.0.30001587", - "electron-to-chromium": "^1.4.668", + "caniuse-lite": "^1.0.30001629", + "electron-to-chromium": "^1.4.796", "node-releases": "^2.0.14", - "update-browserslist-db": "^1.0.13" + "update-browserslist-db": "^1.0.16" } }, "browserstack": { @@ -36772,35 +33629,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 +33660,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.30001633", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001633.tgz", + "integrity": "sha512-6sT0yf/z5jqf8tISAgpJDrmwOpLsrpnyCdD/lOZKvKkkJK4Dn0X5i7KF7THEZhOq+30bmhwBlNEaqPUiHiKtZg==" }, "caseless": { "version": "0.12.0", @@ -36915,6 +33743,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.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.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,45 +33803,23 @@ "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==", + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.6.3.tgz", + "integrity": "sha512-qXlsCmpCZJAnoTYI83Iu6EdYQpMYdVkCfq08KDh2pmlVqK5t5IA9mGs4/LwCwp4fqisSOMXZxP3HIh8w8aRn0A==", "dev": true, + "optional": true, + "peer": true, "requires": { - "mitt": "3.0.0" + "mitt": "3.0.1", + "urlpattern-polyfill": "10.0.0", + "zod": "3.23.8" } }, "ci-info": { @@ -37027,9 +33877,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 +33957,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" + } } } }, @@ -37215,9 +34070,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 +34088,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" } } } @@ -37468,49 +34351,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,9 +34573,9 @@ "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.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", "requires": { "ms": "2.1.2" } @@ -37744,27 +34623,10 @@ "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", - "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, "requires": { "type-detect": "^4.0.0" @@ -37802,12 +34664,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 +34677,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 +34698,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 +34707,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,435 +34815,19 @@ "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 + "dev": true, + "optional": true, + "peer": true }, "di": { "version": "0.0.1", @@ -38742,9 +35186,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 +35339,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 +35368,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.4.802", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.802.tgz", + "integrity": "sha512-TnTMUATbgNdPXVSHsxvNVSG0uEd6cSZsANjm8c9HbvflZVVn1yTRcmVXYT1Ma95/ssB/Dcd30AHweH2TE+dNpA==" }, "emoji-regex": { "version": "8.0.0", @@ -38942,7 +35387,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", @@ -38954,9 +35421,9 @@ } }, "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==", + "version": "6.5.5", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.5.5.tgz", + "integrity": "sha512-C5Pn8Wk+1vKBoHghJODM63yk8MvrO9EWZUfkAt5HAqIgPE4/8FF0PEGHXtEd40l223+cE5ABWuPzm38PHFXfMA==", "dev": true, "requires": { "@types/cookie": "^0.4.1", @@ -38968,7 +35435,7 @@ "cors": "~2.8.5", "debug": "~4.3.1", "engine.io-parser": "~5.2.1", - "ws": "~8.11.0" + "ws": "~8.17.1" }, "dependencies": { "cookie": { @@ -38986,9 +35453,9 @@ "dev": true }, "enhanced-resolve": { - "version": "5.16.1", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.16.1.tgz", - "integrity": "sha512-4U5pNsuDl0EhuZpq46M5xPslstkviJuhrdobaRDBk2Jy2KO37FDAJl4lb2KlNabxT0m4MTK2UHNrsAcphE8nyw==", + "version": "5.17.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz", + "integrity": "sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg==", "dev": true, "requires": { "graceful-fs": "^4.2.4", @@ -39250,6 +35717,38 @@ "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", @@ -39515,9 +36014,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.11.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.11.0.tgz", + "integrity": "sha512-gbBE5Hitek/oG6MUVj6sFuzEjA/ClzNflVrLovHi/JgLdC7fiN5gLAY1WIPW1a0V5I999MnsrvVrCOGmmVqDBQ==", "dev": true, "requires": { "debug": "^3.2.7" @@ -39545,26 +36044,27 @@ } }, "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.30.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.30.0.tgz", + "integrity": "sha512-/mHNE9jINJfiD2EKkg1BKyPyUk4zdnT54YgbOgfjSakWT5oyX/qQLVNTkehyfpcMxZXMy1zyonZ2v7hZTX43Yw==", "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.9.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", "tsconfig-paths": "^3.15.0" }, @@ -39590,19 +36090,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 +36118,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 +36508,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.0", + "resolved": "https://registry.npmjs.org/express/-/express-4.21.0.tgz", + "integrity": "sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==", "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-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 +36554,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 +36780,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 +36832,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 +36886,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 +36916,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 +37067,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 +37200,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 +37240,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 +37284,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", @@ -41494,18 +37455,18 @@ } }, "geckodriver": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.0.tgz", - "integrity": "sha512-Y/Np2VkAhBkJoFAIY3pKH3rICUcR5rH9VD6EHwh0CqUIh6Opzr/NFwfcQenYfbRT/659R15/35LpA1s6h9wPPg==", + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/geckodriver/-/geckodriver-4.4.1.tgz", + "integrity": "sha512-nnAdIrwLkMcDu4BitWXF23pEMeZZ0Cj7HaWWFdSpeedBP9z6ft150JYiGO2mwzw6UiR823Znk1JeIf07RyzloA==", "dev": true, "requires": { "@wdio/logger": "^8.28.0", + "@zip.js/zip.js": "^2.7.44", "decamelize": "^6.0.0", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.4", "node-fetch": "^3.3.2", "tar-fs": "^3.0.6", - "unzipper": "^0.11.4", "which": "^4.0.0" }, "dependencies": { @@ -41603,6 +37564,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 +37678,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": { @@ -41730,9 +37701,9 @@ } }, "minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" @@ -41975,6 +37946,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 +37968,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 +38007,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 } } }, @@ -42197,33 +38164,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 +38492,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 } } }, @@ -43967,16 +39814,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 +39852,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.2.1", + "resolved": "https://registry.npmjs.org/htmlfy/-/htmlfy-0.2.1.tgz", + "integrity": "sha512-HoomFHQ3av1uhq+7FxJTq4Ns0clAD+tGbQNrSd0WFY3UAjjUk6G3LaWEqdgmIXYkY4pexZiyZ3ykZJhQlM0J5A==", "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 +39931,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 +39942,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 +39967,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 +40031,18 @@ "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": "10.1.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-10.1.8.tgz", + "integrity": "sha512-syxGpOzLyqVeZi1KDBjRTnCn5PiGWySGHP0BbqXbqsEK0ckkZk3egAepEWslUjZXj0rhkUapVXM/IpADWe4D6w==", "dev": true, "requires": { - "@ljharb/through": "^2.3.11", + "@inquirer/prompts": "^5.3.8", + "@inquirer/type": "^1.5.2", + "@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 +40062,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 +40182,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 +40256,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 +40286,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 +40445,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 +40706,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", @@ -45312,9 +41111,9 @@ "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": { @@ -45381,6 +41180,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 +41688,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 +41714,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 +41767,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 +41803,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", @@ -46208,18 +41995,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 +42004,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 +42034,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 +42180,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 +42189,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", @@ -46500,14 +42242,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 +42271,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 +42410,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 +42681,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 +42707,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 +43048,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 +43082,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,16 +43107,18 @@ "dev": true }, "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 }, "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 + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "optional": true, + "peer": true }, "mixin-deep": { "version": "1.3.2", @@ -47428,39 +43146,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.7.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.7.3.tgz", + "integrity": "sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A==", "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 +43218,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 +43244,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 +43314,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 +43397,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": { @@ -47826,9 +43516,9 @@ } }, "nan": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz", - "integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==", + "version": "2.20.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.20.0.tgz", + "integrity": "sha512-bk3gXBZDGILuuo/6sKtr0DQmSThYHLtNCdSdXk9YkxD/jK6X2vmCyyXBBxyqZ4XcnzTyYEAThfX3DCEnLf6igw==", "dev": true, "optional": true }, @@ -47873,12 +43563,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 +43646,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" @@ -48060,12 +43744,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 +44030,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 +44039,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 +44054,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 +44131,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 +44163,16 @@ "path-root": "^0.1.1" } }, + "parse-imports": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-imports/-/parse-imports-2.1.0.tgz", + "integrity": "sha512-JQWgmK2o4w8leUkZeZPatWdAny6vXGU/3siIUvMF6J2rDCud9aTt8h/px9oZJ6U3EcfhngBJ635uPFI0q0VAeA==", + "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 +44236,47 @@ "integrity": "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==", "dev": true }, + "parse5-htmlparser2-tree-adapter": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.0.0.tgz", + "integrity": "sha512-B77tOZrqqfUfnVcOrUvfdLbz4pu4RopLD/4vmu3HUPswwTA8OH0EMW9BlWR2B0RCoiZRAHEUu7IxeP1Pd1UU+g==", + "dev": true, + "requires": { + "domhandler": "^5.0.2", + "parse5": "^7.0.0" + }, + "dependencies": { + "parse5": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.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.1.2", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.1.2.tgz", + "integrity": "sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==", + "dev": true, + "requires": { + "entities": "^4.4.0" + } + } + } + }, "parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -48713,17 +44343,17 @@ }, "dependencies": { "lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", "dev": true } } }, "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", @@ -48809,6 +44439,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 +44546,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 +44705,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 +44718,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 +44743,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", @@ -49234,9 +44787,9 @@ }, "dependencies": { "type-fest": { - "version": "4.18.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.18.2.tgz", - "integrity": "sha512-+suCYpfJLAe4OXS6+PPXjW3urOS4IoP9waSiLuXfLgqZODKw/aWwASvzqE886wA0kQgGy0mIWyhd87VpqIy6Xg==", + "version": "4.20.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.20.0.tgz", + "integrity": "sha512-MBh+PHUHHisjXf4tlx0CFWoMdjx8zCMLJHOjnV1prABYZFHqtFOyauCIK2/7w4oIfwkF8iNhLtnJEfVY2vn3iw==", "dev": true } } @@ -49464,12 +45017,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 +45172,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 +45276,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 +45301,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", @@ -49818,9 +45347,9 @@ "dev": true }, "rfdc": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.1.tgz", - "integrity": "sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", "dev": true }, "rgb2hex": { @@ -49879,9 +45408,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 } } @@ -50001,73 +45530,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 +45719,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 +45868,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", @@ -50298,10 +45943,10 @@ "totalist": "^3.0.0" } }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "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": { @@ -50491,13 +46136,13 @@ } }, "socket.io-adapter": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz", - "integrity": "sha512-wDNHGXGewWAjQPt3pyeYBtpWSq9cLE5UW1ZUPL/2eGK9jtse/FpXib7epSTsz0Q0m+6sg6Y4KtcFTlah1bdOVg==", + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.5.tgz", + "integrity": "sha512-eLDQas5dzPgOWCk9GuuJC2lBqItuhKI4uxGgo9aIV7MYbk2h9Q6uULEh8WBzThoI7l+qU9Ast9fVUmkqPP9wYg==", "dev": true, "requires": { "debug": "~4.3.4", - "ws": "~8.11.0" + "ws": "~8.17.1" } }, "socket.io-parser": { @@ -50571,15 +46216,6 @@ "decode-uri-component": "^0.2.0" } }, - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - }, "source-map-url": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", @@ -50631,9 +46267,9 @@ } }, "spdx-license-ids": { - "version": "3.0.17", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz", - "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==", + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", "dev": true }, "split": { @@ -50837,14 +46473,15 @@ } }, "streamx": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", - "integrity": "sha512-m9QYj6WygWyWa3H1YY69amr4nVgy61xfjys7xO7kviL5rfIEc2naf+ewFiOA+aEJD7y0JO3h2GoiUv4TDwEGzQ==", + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.18.0.tgz", + "integrity": "sha512-LLUC1TWdjVdn1weXGcSxyTR3T4+acB6tVGXT95y0nGbca4t4o/ng1wKAGTljm9VicuCVLvRlqFYXYy5GwgM7sQ==", "dev": true, "requires": { "bare-events": "^2.2.0", - "fast-fifo": "^1.1.0", - "queue-tick": "^1.0.1" + "fast-fifo": "^1.3.2", + "queue-tick": "^1.0.1", + "text-decoder": "^1.1.0" } }, "strict-event-emitter": { @@ -51007,9 +46644,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,6 +46655,12 @@ "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", @@ -51041,6 +46684,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", @@ -51055,9 +46716,9 @@ }, "dependencies": { "ajv": { - "version": "8.13.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.13.0.tgz", - "integrity": "sha512-PRA911Blj99jR5RMeTunVbNXMF6Lp4vZXnk5GQjcnUWUTsrXtekg/pnmFFI2u/I36Y/2bITGS30GZCXei6uNkA==", + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.16.0.tgz", + "integrity": "sha512-F0twR8U1ZU67JIEtekUcLkXkoO5mMMmgGD8sK/xUFzJ805jxHQl92hImFAqqXMyMYjSPOyUPAwHYhB72g5sTXw==", "dev": true, "requires": { "fast-deep-equal": "^3.1.3", @@ -51170,9 +46831,9 @@ } }, "terser": { - "version": "5.31.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.0.tgz", - "integrity": "sha512-Q1JFAoUKE5IMfI4Z/lkE/E6+SwgzO+x4tq4v1AyBLRj8VSYvRO6A/rQrPg1yud4g0En9EKI1TvFRF2tQFcoUkg==", + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", "dev": true, "requires": { "@jridgewell/source-map": "^0.3.3", @@ -51240,15 +46901,6 @@ "ajv": "^6.12.5", "ajv-keywords": "^3.5.2" } - }, - "serialize-javascript": { - "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" - } } } }, @@ -51279,6 +46931,15 @@ } } }, + "text-decoder": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.1.0.tgz", + "integrity": "sha512-TmLJNj6UgX8xcUZo4UDStGQtDiTzF7BzWlzn9g7UWrjkpHr5uJTK1ld16wZ3LXb2vb6jH8qU89dW5whuMdXYdw==", + "dev": true, + "requires": { + "b4a": "^1.6.4" + } + }, "text-table": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", @@ -51348,13 +47009,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 +47048,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", @@ -51509,12 +47176,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 +47188,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 +47234,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", @@ -51595,9 +47261,9 @@ "dev": true }, "type": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/type/-/type-2.7.2.tgz", - "integrity": "sha512-dzlvlNlt6AXU7EBSfpAscydQ7gXB+pPGsPnfJnZpiNJBDj7IaJzQlBZYGdEi4R9HmPdBv2XmWJ6YUtoTa7lmCw==", + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz", + "integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==", "dev": true }, "type-check": { @@ -51710,15 +47376,15 @@ } }, "ua-parser-js": { - "version": "0.7.37", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.37.tgz", - "integrity": "sha512-xV8kqRKM+jhMvcHWUKthV9fNebIzrNy//2O9ZwWcfiBFR5f25XVZPLlEajk/sf3Ra15V92isyQqnIEXRDaZWEA==", + "version": "0.7.38", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.38.tgz", + "integrity": "sha512-fYmIy7fKTSFAhG3fuPlubeGaMoAd6r0rSnfEsO5nEY55i26KSLt9EH7PLQiiqPUhNqYIJvSkTy1oArIcXAbPbA==", "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 +47448,12 @@ "integrity": "sha512-UR1khWeAjugW3548EfQmL9Z7pGMlBgXteQpr1IZeZBtnkCJQJIJ1Scj0mb9wQaPvUZ9Q17XqW6TIaPchJkyfqw==", "dev": true }, + "undici": { + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.19.8.tgz", + "integrity": "sha512-U8uCCl2x9TK3WANvmBavymRzxbfFYG+tAu+fgx3zxQy3qdagQqBLwJVrdyO1TBfUXvfKveMKJZhpvUYoOjM+4g==", + "dev": true + }, "undici-types": { "version": "5.26.5", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", @@ -51812,6 +47484,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", @@ -51983,36 +47661,6 @@ } } }, - "unzipper": { - "version": "0.11.6", - "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.11.6.tgz", - "integrity": "sha512-anERl79akvqLbAxfjIFe4hK0wsi0fH4uGLwNEl4QEnG+KKs3QQeApYgOS/f6vH2EdACUlZg35psmd/3xL2duFQ==", - "dev": true, - "requires": { - "big-integer": "^1.6.17", - "bluebird": "~3.4.1", - "duplexer2": "~0.1.4", - "fstream": "^1.0.12", - "graceful-fs": "^4.2.2" - }, - "dependencies": { - "bluebird": { - "version": "3.4.7", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz", - "integrity": "sha512-iD3898SR7sWVRHbiQv+sHUtHnMvC1o3nW5rAcqnq3uOn07DSAppZYUkIGslDz6gXC7HfunPe7YVBgoEJASPcHA==", - "dev": true - }, - "duplexer2": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.1.4.tgz", - "integrity": "sha512-asLFVfWWtJ90ZyOUHMqk7/S2w2guQKxUI2itj3d92ADHhxUSbCMGi1f1cBcJ7xM1To+pE/Khbwo1yuNbMEPKeA==", - "dev": true, - "requires": { - "readable-stream": "^2.0.2" - } - } - } - }, "upath": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/upath/-/upath-1.2.0.tgz", @@ -52058,15 +47706,6 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==", "dev": true - }, - "qs": { - "version": "6.12.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.12.1.tgz", - "integrity": "sha512-zWmv4RSuB9r2mYQw3zxQuHWeU+42aKi1wWig/j4ele4ygELZ7PEO6MM7rim9oAQH2A5MWfsAVf/jPvTPgCbvUQ==", - "dev": true, - "requires": { - "side-channel": "^1.0.6" - } } } }, @@ -52086,6 +47725,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", @@ -52288,13 +47933,13 @@ } }, "video.js": { - "version": "7.21.5", - "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.5.tgz", - "integrity": "sha512-WRq86tXZKrThA9mK+IR+v4tIQVVvnb5LhvL71fD2AX7TxVOPdaeK1X/wyuUruBqWaOG3w2sZXoMY6HF2Jlo9qA==", + "version": "7.21.6", + "resolved": "https://registry.npmjs.org/video.js/-/video.js-7.21.6.tgz", + "integrity": "sha512-m41TbODrUCToVfK1aljVd296CwDQnCRewpIm5tTXMuV87YYSGw1H+VDOaV45HlpcWSsTWWLF++InDgGJfthfUw==", "dev": true, "requires": { "@babel/runtime": "^7.12.5", - "@videojs/http-streaming": "2.16.2", + "@videojs/http-streaming": "2.16.3", "@videojs/vhs-utils": "^3.0.4", "@videojs/xhr": "2.6.0", "aes-decrypter": "3.1.3", @@ -52336,23 +47981,21 @@ "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": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.1.tgz", - "integrity": "sha512-HNfIHlrdzveYjWhGx23tPvI1TxZdrXIEfUdm55azcTniDDuIR/qJYVdkOdv7q7PqHvJaF89GmpJDeGBzd9s25w==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/videojs-playlist/-/videojs-playlist-5.1.2.tgz", + "integrity": "sha512-8YgNq/iL17RLTXpfWAkuhM0Sq4w/x5YPVaNbUycjfqqGL/bp3Nrmc2W0qkPfh0ryB7r4cHfJbtHYP7zlW3ZkdQ==", "dev": true, "requires": { "global": "^4.3.2", @@ -52585,6 +48228,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,399 +48246,429 @@ "dev": true }, "webdriver": { - "version": "8.36.1", - "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-8.36.1.tgz", - "integrity": "sha512-547RivYCHStVqtiGQBBcABAkzJbPnAWsxpXGzmj5KL+TOM2JF41N2iQRtUxXqr0jme1Nzzye7WS7Y7iSnK6i1g==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.5.tgz", + "integrity": "sha512-+xkdfbmG1IZrXxiPwab450Xuh9QClOcxTJ6tnde0rzxlPxdUqZqzwuMtM+VXZybxF4yCLrJWbeT0BpwJFAz1nA==", "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.0.5", + "@wdio/logger": "9.0.4", + "@wdio/protocols": "9.0.4", + "@wdio/types": "9.0.4", + "@wdio/utils": "9.0.5", + "deepmerge-ts": "^7.0.3", "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==", + "@puppeteer/browsers": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.3.1.tgz", + "integrity": "sha512-uK7o3hHkK+naEobMSJ+2ySYyXtQkBxIH8Gn4MK9ciePjNV+Pf+PgY/W7iPzn2MTjl3stcYB5AlcTmPYw7AXDwA==", "dev": true, "requires": { - "defer-to-connect": "^2.0.0" + "debug": "^4.3.6", + "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" } }, - "@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==", + "@wdio/logger": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.4.tgz", + "integrity": "sha512-b6gcu0PTVb3fgK4kyAH/k5UUWN5FOUdAfhA4PAY/IZvxZTMFYMqnrZb0WRWWWqL6nu9pcrOVtCOdPBvj0cb+Nw==", "dev": true, "requires": { - "undici-types": "~5.26.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "@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==", + "@wdio/types": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.4.tgz", + "integrity": "sha512-MN7O4Uk3zPWvkN8d6SNdIjd7qHUlTxS7j0QfRPu6TdlYbHu6BJJ8Rr84y7GcUzCnTAJ1nOIpvUyR8MY3hOaVKg==", "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" + "@types/node": "^20.1.0" } }, - "@wdio/logger": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-7.26.0.tgz", - "integrity": "sha512-kQj9s5JudAG9qB+zAAcYGPHVfATl2oqKgqj47yjehOQ1zzG33xmtL1ArFbQKWhDG32y1A8sN6b0pIqBEIwgg8Q==", + "@wdio/utils": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.5.tgz", + "integrity": "sha512-FOA+t2ixLZ9a7eEH4IZXDsR/ABPTFOTslVzRvIDIkXcxGys3Cn3LQP2tpcIV1NxI+7OOAD0fIZ9Ig3gPBoVZRQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.4", + "@wdio/types": "9.0.4", + "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" + } + }, + "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": { - "chalk": "^4.0.0", - "loglevel": "^1.6.0", - "loglevel-plugin-prefix": "^0.8.4", - "strip-ansi": "^6.0.0" + "debug": "^4.3.4" } }, - "@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==", + "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/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" - } + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "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==", + "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": { - "@types/node": "^18.0.0", - "got": "^11.8.1" + "agent-base": "^7.0.2", + "debug": "4" } }, - "@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==", + "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 + }, + "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": { - "@wdio/logger": "7.26.0", - "@wdio/types": "7.33.0", - "p-iteration": "^1.1.8" + "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" } }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "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": { - "color-convert": "^2.0.1" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "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": { - "balanced-match": "^1.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" } - }, - "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==", + } + } + }, + "webdriverio": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/webdriverio/-/webdriverio-9.0.9.tgz", + "integrity": "sha512-IwvKzhcJ9NjOL55xwj27uTTKkfxsg77dmAfqoKFSP5dQ70JzU+NgxiALEjjWQDybtt1yGIkHk7wjjxjboMU1uw==", + "dev": true, + "requires": { + "@types/node": "^20.11.30", + "@types/sinonjs__fake-timers": "^8.1.5", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/repl": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.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.2.1", + "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.0.8" + }, + "dependencies": { + "@puppeteer/browsers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.4.0.tgz", + "integrity": "sha512-x8J1csfIygOwf6D6qUAZ0ASk3z63zPb7wkNeHRerCMh82qWKUrOgkuP005AJC8lDL6/evtXETGEJVcwykKT4/g==", "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" + "debug": "^4.3.6", + "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" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "@wdio/config": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/config/-/config-9.0.8.tgz", + "integrity": "sha512-37L+hd+A1Nyehd/pgfTrLC6w+Ngbu0CIoFh9Vv6v8Cgu5Hih0TLofvlg+J1BNbcTd5eQ2tFKZBDeFMhQaIiTpg==", "dev": true, "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "decamelize": "^6.0.0", + "deepmerge-ts": "^7.0.3", + "glob": "^10.2.2", + "import-meta-resolve": "^4.0.0" } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "@wdio/logger": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/logger/-/logger-9.0.8.tgz", + "integrity": "sha512-uIyYIDBwLczmsp9JE5hN3ME8Xg+9WNBfSNXD69ICHrY9WPTzFf94UeTuavK7kwSKF3ro2eJbmNZItYOfnoovnw==", "dev": true, "requires": { - "color-name": "~1.1.4" + "chalk": "^5.1.2", + "loglevel": "^1.6.0", + "loglevel-plugin-prefix": "^0.8.4", + "strip-ansi": "^7.1.0" } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "@wdio/protocols": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/protocols/-/protocols-9.0.8.tgz", + "integrity": "sha512-xRH54byFf623/w/KW62xkf/C2mGyigSfMm+UT3tNEAd5ZA9X2VAWQWQBPzdcrsck7Fxk4zlQX8Kb34RSs7Cy4Q==", "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==", + "@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, "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" + "@types/node": "^20.1.0" } }, - "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==", + "@wdio/types": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/types/-/types-9.0.8.tgz", + "integrity": "sha512-pmz2iRWddTanrv8JC7v3wUGm17KRv2WyyJhQfklMSANn9V1ep6pw1RJG2WJnKq4NojMvH1nVv1sMZxXrYPhpYw==", "dev": true, "requires": { - "pump": "^3.0.0" + "@types/node": "^20.1.0" } }, - "glob": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", - "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", + "@wdio/utils": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/@wdio/utils/-/utils-9.0.8.tgz", + "integrity": "sha512-p3EgOdkhCvMxJFd3WTtSChqYFQu2mz69/5tOsljDaL+4QYwnRR7O8M9wFsL3/9XMVcHdnC4Ija2VRxQ/lb+hHQ==", + "dev": true, + "requires": { + "@puppeteer/browsers": "^2.2.0", + "@wdio/logger": "9.0.8", + "@wdio/types": "9.0.8", + "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" + } + }, + "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": { - "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" - } - } + "debug": "^4.3.4" } }, - "got": { - "version": "11.8.6", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", - "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", + "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": { - "@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" + "balanced-match": "^1.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==", + "chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", "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" - } + "deepmerge-ts": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.0.tgz", + "integrity": "sha512-q6bNsfNBtgr8ZOQqmZbl94MmYWm+QcDNIkqCxVWiw1vKvf+y/N2dZQKdnDXn4c5Ygt/y63tDof6OCN+2YwWVEg==", + "dev": true }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "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": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "agent-base": "^7.0.2", + "debug": "4" } }, - "ky": { - "version": "0.30.0", - "resolved": "https://registry.npmjs.org/ky/-/ky-0.30.0.tgz", - "integrity": "sha512-X/u76z4JtDVq10u1JA5UQfatPxgPaVDMYTrgHyiTpGN2z4TMEJkIHsoSBBSg9SWZEIXTKsi9kHgiQ9o3Y/4yog==", - "dev": true - }, - "lowercase-keys": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "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": "6.2.0", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-6.2.0.tgz", - "integrity": "sha512-sauLxniAmvnhhRjFwPNnJKaPFYyddAgbYdeUpHULtCT/GhzdCx/MDNy+Y40lBxTQUrMzDE8e0S43Z5uqfO0REg==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", "dev": true, "requires": { "brace-expansion": "^2.0.1" } }, - "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 - }, - "responselike": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "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": { - "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" } }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "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": { - "ansi-regex": "^5.0.1" + "bare-fs": "^2.1.1", + "bare-path": "^2.1.0", + "pump": "^3.0.0", + "tar-stream": "^3.1.5" } }, - "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==", + "webdriver": { + "version": "9.0.8", + "resolved": "https://registry.npmjs.org/webdriver/-/webdriver-9.0.8.tgz", + "integrity": "sha512-UnV0ANriSTUgypGk0pz8lApeQuHt+72WEDQG5hFwkkSvggtKLyWdT7+PQkNoXvDajTmiLIqUOq8XPI/Pm71rtw==", "dev": true, "requires": { - "has-flag": "^4.0.0" + "@types/node": "^20.1.0", + "@types/ws": "^8.5.3", + "@wdio/config": "9.0.8", + "@wdio/logger": "9.0.8", + "@wdio/protocols": "9.0.8", + "@wdio/types": "9.0.8", + "@wdio/utils": "9.0.8", + "deepmerge-ts": "^7.0.3", + "ws": "^8.8.0" } }, - "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", - "integrity": "sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw==", + "version": "5.94.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.94.0.tgz", + "integrity": "sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg==", "dev": true, "requires": { - "@types/eslint-scope": "^3.7.3", "@types/estree": "^1.0.5", "@webassemblyjs/ast": "^1.12.1", "@webassemblyjs/wasm-edit": "^1.12.1", "@webassemblyjs/wasm-parser": "^1.12.1", "acorn": "^8.7.1", - "acorn-import-assertions": "^1.9.0", + "acorn-import-attributes": "^1.9.5", "browserslist": "^4.21.10", "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.16.0", + "enhanced-resolve": "^5.17.1", "es-module-lexer": "^1.2.1", "eslint-scope": "5.1.1", "events": "^3.2.0", @@ -53017,10 +48691,10 @@ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", "dev": true }, - "acorn-import-assertions": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz", - "integrity": "sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==", + "acorn-import-attributes": { + "version": "1.9.5", + "resolved": "https://registry.npmjs.org/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz", + "integrity": "sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==", "dev": true, "requires": {} }, @@ -53094,9 +48768,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 +48912,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 +49028,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": { @@ -53451,9 +49141,9 @@ } }, "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", + "version": "8.17.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", + "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, "requires": {} }, @@ -53534,62 +49224,71 @@ "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" } } } }, + "zod": { + "version": "3.23.8", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz", + "integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==", + "dev": true, + "optional": true, + "peer": true + }, "zwitch": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", diff --git a/package.json b/package.json index 12a2f1296e5..3ca7aa59886 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,17 @@ { "name": "prebid.js", - "version": "8.49.84", + "version": "9.15.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", @@ -67,7 +76,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", @@ -102,20 +111,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", @@ -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": "^6.7.3" }, "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/prebid-analytics-9.15.0.js b/prebid-analytics-9.15.0.js new file mode 100644 index 00000000000..da375bc7a31 --- /dev/null +++ b/prebid-analytics-9.15.0.js @@ -0,0 +1,107 @@ +/* prebid.js v9.15.0 +Updated: 2024-10-11 +Modules: videoModule, fpdModule, 33acrossIdSystem, adprimeBidAdapter, adyoulikeBidAdapter, airgridRtdProvider, appnexusBidAdapter, atsAnalyticsAdapter, carbonAnalyticsAdapter, carbonRtdProvider, concertBidAdapter, connectIdSystem, consentManagementUsp, consentManagementTcf, conversantBidAdapter, criteoBidAdapter, criteoIdSystem, dfpAdServerVideo, fabrickIdSystem, gptPreAuction, gridBidAdapter, gumgumBidAdapter, hadronIdSystem, hadronRtdProvider, id5IdSystem, identityLinkIdSystem, insticatorBidAdapter, intentIqIdSystem, ixBidAdapter, justpremiumBidAdapter, kargoBidAdapter, linkedInAdsIdSystem, liveIntentIdSystem, medianetBidAdapter, openxBidAdapter, pairIdSystem, prebidServerBidAdapter, priceFloors, pubmaticBidAdapter, pubProvidedIdSystem, riseBidAdapter, rtdModule, rubiconBidAdapter, schain, seedtagBidAdapter, sharedIdSystem, sharethroughBidAdapter, sizeMappingV2, sonobiBidAdapter, teadsBidAdapter, topicsFpdModule, tripleliftBidAdapter, ttdBidAdapter, uid2IdSystem, undertoneBidAdapter, unifiedIdSystem, unrulyBidAdapter, userId, vidazooBidAdapter, videojsVideoProvider, yahooAdsBidAdapter, yieldmoBidAdapter, pulsepointBidAdapter, pulsepointAnalyticsAdapter */ +if(window.fsprebid&&window.fsprebid.libLoaded)try{window.fsprebid.getConfig("debug")&&console.warn("Attempted to load a copy of Prebid.js that clashes with the existing 'fsprebid' instance. Load aborted.")}catch(e){}else (function(){ +(()=>{var r,t={433:(r,t,e)=>{function n(r,t,e,n,o){for(t=t.split?t.split("."):t,n=0;nn})},8128:r=>{ +/* +* @license MIT +* Fun Hooks v0.9.10 +* (c) @snapwich +*/ +f.SYNC=1,f.ASYNC=2,f.QUEUE=4;var t="fun-hooks";var e=Object.freeze({useProxy:!0,ready:0}),n=new WeakMap,o="2,1,0"===[1].reduce((function(r,t,e){return[r,t,e]}),2).toString()?Array.prototype.reduce:function(r,t){var e,n=Object(this),o=n.length>>>0,i=0;if(t)e=t;else{for(;i{function n(r){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(r){return typeof r}:function(r){return r&&"function"==typeof Symbol&&r.constructor===Symbol&&r!==Symbol.prototype?"symbol":typeof r},n(r)}function o(r){var t=function(r,t){if("object"!=n(r)||!r)return r;var e=r[Symbol.toPrimitive];if(void 0!==e){var o=e.call(r,t||"default");if("object"!=n(o))return o;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(r)}(r,"string");return"symbol"==n(t)?t:t+""}function i(r,t,e){return(t=o(t))in r?Object.defineProperty(r,t,{value:e,enumerable:!0,configurable:!0,writable:!0}):r[t]=e,r}e.d(t,{A:()=>i})},3172:(r,t,e)=>{function n(r,t,e){t.split&&(t=t.split("."));for(var n,o,i=0,u=t.length,f=r;in})},5751:(r,t,e)=>{function n(r){var t,e,o;if(Array.isArray(r)){for(e=Array(t=r.length);t--;)e[t]=(o=r[t])&&"object"==typeof o?n(o):o;return e}if("[object Object]"===Object.prototype.toString.call(r)){for(t in e={},r)"__proto__"===t?Object.defineProperty(e,t,{value:n(r[t]),configurable:!0,enumerable:!0,writable:!0}):e[t]=(o=r[t])&&"object"==typeof o?n(o):o;return e}return r}e.d(t,{Q:()=>n})}},e={};function n(r){var o=e[r];if(void 0!==o)return o.exports;var i=e[r]={exports:{}};return t[r].call(i.exports,i,i.exports,n),i.exports}n.m=t,r=[],n.O=(t,e,o,i)=>{if(!e){var u=1/0;for(l=0;l=i)&&Object.keys(n.O).every((r=>n.O[r](e[c])))?e.splice(c--,1):(f=!1,i0&&r[l-1][2]>i;l--)r[l]=r[l-1];r[l]=[e,o,i]},n.n=r=>{var t=r&&r.__esModule?()=>r.default:()=>r;return n.d(t,{a:t}),t},n.d=(r,t)=>{for(var e in t)n.o(t,e)&&!n.o(r,e)&&Object.defineProperty(r,e,{enumerable:!0,get:t[e]})},n.g=function(){if("object"==typeof globalThis)return globalThis;try{return this||new Function("return this")()}catch(r){if("object"==typeof window)return window}}(),n.o=(r,t)=>Object.prototype.hasOwnProperty.call(r,t),n.r=r=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(r,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(r,"__esModule",{value:!0})},(()=>{var r={9673:0};n.O.j=t=>0===r[t];var t=(t,e)=>{var o,i,u=e[0],f=e[1],c=e[2],a=0;if(u.some((t=>0!==r[t]))){for(o in f)n.o(f,o)&&(n.m[o]=f[o]);if(c)var l=c(n)}for(t&&t(e);an(9671)));o=n.O(o)})(); +(self.fsprebidChunk=self.fsprebidChunk||[]).push([[802],{5789:(e,t,n)=>{n.d(t,{A4:()=>c,J7:()=>u,Pg:()=>l});var i=n(1580),r=n(1069),o=n(433),s=n(5901);const a=(0,n(7873).m)(),d="outstream";function c(e){const{url:t,config:n,id:c,callback:u,loaded:l,adUnitCode:f,renderNow:g}=e;this.url=t,this.config=n,this.handlers={},this.id=c,this.renderNow=g,this.loaded=l,this.cmd=[],this.push=e=>{"function"==typeof e?this.loaded?e.call():this.cmd.push(e):(0,r.vV)("Commands given to Renderer.push must be wrapped in a function")},this.callback=u||(()=>{this.loaded=!0,this.process()}),this.render=function(){const e=arguments,n=()=>{this._render?this._render.apply(this,e):(0,r.JE)("No render function was provided, please use .setRender on the renderer")};!function(e){const t=a.adUnits,n=(0,s.I6)(t,(t=>t.code===e));if(!n)return!1;const i=(0,o.A)(n,"renderer"),r=!!(i&&i.url&&i.render),d=(0,o.A)(n,"mediaTypes.video.renderer"),c=!!(d&&d.url&&d.render);return!!(r&&!0!==i.backupOnly||c&&!0!==d.backupOnly)}(f)?g?n():(this.cmd.unshift(n),(0,i.R)(t,d,this.callback,this.documentContext)):((0,r.JE)(`External Js not loaded by Renderer since renderer url and callback is already defined on adUnit ${f}`),n())}.bind(this)}function u(e){return!(!e||!e.url&&!e.renderNow)}function l(e,t,n){let i=null;e.config&&e.config.documentResolver&&(i=e.config.documentResolver(t,document,n)),i||(i=document),e.documentContext=i,e.render(t,e.documentContext)}c.install=function(e){let{url:t,config:n,id:i,callback:r,loaded:o,adUnitCode:s,renderNow:a}=e;return new c({url:t,config:n,id:i,callback:r,loaded:o,adUnitCode:s,renderNow:a})},c.prototype.getConfig=function(){return this.config},c.prototype.setRender=function(e){this._render=e},c.prototype.setEventHandlers=function(e){this.handlers=e},c.prototype.handleVideoEvent=function(e){let{id:t,eventName:n}=e;"function"==typeof this.handlers[n]&&this.handlers[n](),(0,r.OG)(`Prebid Renderer event for id ${t} type ${n}`)},c.prototype.process=function(){for(;this.cmd.length>0;)try{this.cmd.shift().call()}catch(e){(0,r.vV)("Error processing Renderer command: ",e)}}},6811:(e,t,n)=>{n.d(t,{DL:()=>u,Ml:()=>r,Ue:()=>i,VJ:()=>f,hE:()=>l,hq:()=>c,mo:()=>d,qX:()=>o,uc:()=>a,yl:()=>s});const i="accessDevice",r="syncUser",o="enrichUfpd",s="enrichEids",a="fetchBids",d="reportAnalytics",c="transmitEids",u="transmitUfpd",l="transmitPreciseGeo",f="transmitTid"},3441:(e,t,n)=>{n.d(t,{s:()=>r});var i=n(1445);const r=(0,n(2604).ZI)((e=>i.Ay.resolveAlias(e)))},5569:(e,t,n)=>{n.d(t,{Tn:()=>a,fW:()=>o,tW:()=>r,tp:()=>i,zu:()=>s});const i="prebid",r="bidder",o="userId",s="rtd",a="analytics"},2604:(e,t,n)=>{n.d(t,{Dk:()=>s,Ii:()=>o,TQ:()=>g,XG:()=>u,ZI:()=>p,Zw:()=>c,bt:()=>l,e3:()=>f,iK:()=>a,q7:()=>d});var i=n(5569),r=n(9214);const o="component",s=o+"Type",a=o+"Name",d="adapterCode",c="storageType",u="configName",l="syncType",f="syncUrl",g="_config";function p(e){return function(t,n,r){const c={[s]:t,[a]:n,[o]:`${t}.${n}`};return t===i.tW&&(c[d]=e(n)),h(Object.assign(c,r))}}const h=(0,r.A_)("sync",(e=>e))},6953:(e,t,n)=>{n.d(t,{Vx:()=>d,l7:()=>a,p4:()=>m,$V:()=>h,nl:()=>g,ZP:()=>b,$p:()=>y,uD:()=>p});var i=n(433),r=n(3272),o=n(5139),s=n(6811);const a=["data","ext.data","yob","gender","keywords","kwarray","id","buyeruid","customdata"].map((e=>`user.${e}`)).concat("device.ext.cdep"),d=["user.eids","user.ext.eids"],c=["user.geo.lat","user.geo.lon","device.geo.lat","device.geo.lon"],u=["device.ip"],l=["device.ipv6"];function f(e){return Object.assign({get(){},run(e,t,n,i,r){const o=n&&n[i];if(h(o)&&r()){const e=this.get(o);void 0===e?delete n[i]:n[i]=e}}},e)}function g(e){return e.forEach((e=>{e.paths=e.paths.map((e=>{const t=e.split("."),n=t.pop();return[t.length>0?t.join("."):null,n]}))})),function(t,n){const r=[];for(var o=arguments.length,s=new Array(o>2?o-2:0),a=2;a{if(!1!==t[e.name])for(const[o,s]of e.paths){const a=null==o?n:(0,i.A)(n,o);if(r.push(e.run(n,o,a,s,d.bind(null,e))),!1===t[e.name])return}})),r.filter((e=>null!=e))}}function p(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),i=1;i0)}function m(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:o.io;return function(n){return!t(e,n)}}function b(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o.io;return[{name:s.DL,paths:a,applies:m(s.DL,e)},{name:s.hq,paths:d,applies:m(s.hq,e)},{name:s.hE,paths:c,applies:m(s.hE,e),get:e=>Math.round(100*(e+Number.EPSILON))/100},{name:s.hE,paths:u,applies:m(s.hE,e),get:e=>function(e){if(!e)return null;let t=e.split(".").map(Number);if(4!=t.length)return null;let n=[];for(let e=0;e<4;e++){let t=Math.max(0,Math.min(8,24-8*e));n.push(255<<8-t&255)}return t.map(((e,t)=>e&n[t])).join(".")}(e)},{name:s.hE,paths:l,applies:m(s.hE,e),get:e=>function(e){if(!e)return null;let t=e.split(":").map((e=>parseInt(e,16)));for(t=t.map((e=>isNaN(e)?0:e));t.length<8;)t.push(0);if(8!=t.length)return null;let n=[];for(let e=0;e<8;e++){let t=Math.max(0,Math.min(16,64-16*e));n.push(65535<<16-t&65535)}return t.map(((e,t)=>e&n[t])).map((e=>e.toString(16))).join(":")}(e)},{name:s.VJ,paths:["source.tid"],applies:m(s.VJ,e)}].map(f)}const y=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o.io;const t=g(b(e)),n=g(function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:o.io;return[{name:s.hq,paths:["userId","userIdAsEids"],applies:m(s.hq,e)},{name:s.VJ,paths:["ortb2Imp.ext.tid"],applies:m(s.VJ,e)}].map(f)}(e));return function(e){const i={};return{ortb2:n=>(t(i,n,e),n),bidRequest:t=>(n(i,t,e),t)}}}();(0,o.qB)(s.VJ,"enableTIDs config",(()=>{if(!r.$W.getConfig("enableTIDs"))return{allow:!1,reason:"TIDs are disabled"}}))},5139:(e,t,n)=>{n.d(t,{io:()=>s,qB:()=>o});var i=n(1069),r=n(2604);const[o,s]=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:(0,i.h0)("Activity control:");const t={};function n(e){return t[e]=t[e]||[]}function o(t,n,i,o){let s;try{s=i(o)}catch(i){e.logError(`Exception in rule ${n} for '${t}'`,i),s={allow:!1,reason:i}}return s&&Object.assign({activity:t,name:n,component:o[r.Ii]},s)}const s={};function a(t){let{activity:n,name:i,allow:r,reason:o,component:a}=t;const d=`${i} ${r?"allowed":"denied"} '${n}' for '${a}'${o?":":""}`,c=s.hasOwnProperty(d);if(c&&clearTimeout(s[d]),s[d]=setTimeout((()=>delete s[d]),1e3),!c){const t=[d];o&&t.push(o),(r?e.logInfo:e.logWarn).apply(e,t)}}return[function(e,t,i){let r=arguments.length>3&&void 0!==arguments[3]?arguments[3]:10;const o=n(e),s=o.findIndex((e=>{let[t]=e;return r=0&&o.splice(e,1)}},function(e,t){let i,r;for(const[s,d,c]of n(e)){if(i!==s&&r)break;i=s;const n=o(e,d,c,t);if(n){if(!n.allow)return a(n),!1;r=n}}return r&&a(r),!0}]}()},9075:(e,t,n)=>{n.d(t,{$A:()=>w,BS:()=>x,Hh:()=>j,Uc:()=>O,XO:()=>N,_0:()=>R,bw:()=>D,qn:()=>U,vB:()=>q,vW:()=>B,vd:()=>S});var i=n(1069),r=n(433),o=n(5023),s=n(8969),a=n(3272),d=n(5789),c=n(1371),u=n(8462),l=n(6031),f=n(9214),g=n(2449),p=n(5555),h=n(1445),m=n(6894);const{AD_RENDER_FAILED:b,AD_RENDER_SUCCEEDED:y,STALE_RENDER:v,BID_WON:E}=s.qY,{EXCEPTION:A}=s.as,w=(0,f.A_)("sync",(function(e){return(arguments.length>2&&void 0!==arguments[2]?arguments[2]:p.k.resolve()).then((t=>t??u.n.findBidByAdId(e))).catch((()=>{}))})),T=(0,f.A_)("sync",(function(e){o.emit(E,e),u.n.addWinningBid(e)}));function I(e){let{reason:t,message:n,bid:r,id:s}=e;const a={reason:t,message:n};r&&(a.bid=r,a.adId=r.adId),s&&(a.adId=s),(0,i.vV)(`Error rendering ad (id: ${s}): ${n}`),o.emit(b,a)}function C(e){let{doc:t,bid:n,id:i}=e;const r={doc:t};n&&(r.bid=n),i&&(r.adId=i),h.Ay.callAdRenderSucceededBidder(n.adapterCode||n.bidder,n),o.emit(y,r)}function O(e,t){switch(e.event){case s.qY.AD_RENDER_FAILED:I({bid:t,id:t.adId,reason:e.info.reason,message:e.info.message});break;case s.qY.AD_RENDER_SUCCEEDED:C({doc:null,bid:t,id:t.adId});break;default:(0,i.vV)(`Received event request for unsupported event: '${e.event}' (adId: '${t.adId}')`)}}function B(e,t,n){let{resizeFn:i,fireTrackers:r=g.vO}=n;if("resizeNativeHeight"===e.action)i(e.width,e.height);else r(e,t)}const k={[s.nl.EVENT]:O};k[s.nl.NATIVE]=B;const S=(0,f.A_)("sync",(function(e,t){const{ad:n,adUrl:r,cpm:o,originalCpm:s,width:a,height:d}=e,c={AUCTION_PRICE:s||o,CLICKTHROUGH:t?.clickUrl||""};return{ad:(0,i.gM)(n,c),adUrl:(0,i.gM)(r,c),width:a,height:d}})),R=(0,f.A_)("sync",(function(e){let{renderFn:t,resizeFn:n,bidResponse:i,options:r}=e;if(i.mediaType===c.G_)return void I({reason:s.as.PREVENT_WRITING_ON_MAIN_DOCUMENT,message:"Cannot render video ad",bid:i,id:i.adId});const o=S(i,r);t(Object.assign({adId:i.adId},o));const{width:a,height:d}=o;null!=(a??d)&&n(a,d)}));function D(e){let{renderFn:t,resizeFn:n,adId:d,options:c,bidResponse:u,doc:l}=e;j(u,(()=>{if(null!=u){if(u.status!==s.tl.RENDERED||((0,i.JE)(`Ad id ${d} has been rendered before`),o.emit(v,u),!(0,r.A)(a.$W.getConfig("auctionOptions"),"suppressStaleRender")))try{R({renderFn:t,resizeFn:n,bidResponse:u,options:c,doc:l})}catch(e){I({reason:s.as.EXCEPTION,message:e.message,id:d,bid:u})}}else I({reason:s.as.CANNOT_FIND_AD,message:`Cannot find ad '${d}'`,id:d})}))}function U(e){const t=(0,m.BO)(e.metrics);t.checkpoint("bidRender"),t.timeBetween("bidWon","bidRender","render.deferred"),t.timeBetween("auctionEnd","bidRender","render.pending"),t.timeBetween("requestBids","bidRender","render.e2e"),e.status=s.tl.RENDERED}R.before((function(e,t){const{bidResponse:n,doc:i}=t;(0,d.J7)(n.renderer)?((0,d.Pg)(n.renderer,n,i),C({doc:i,bid:n,id:n.adId}),e.bail()):e(t)}),100);const _=new WeakMap,$=new WeakSet;function j(e,t){null!=e?(_.set(e,t),e.deferRendering||q(e),$.has(e)||($.add(e),T(e))):t()}function q(e){const t=_.get(e);t&&(t(),U(e),_.delete(e))}function x(e,t,n){let r;function o(e,n){I(Object.assign({id:t,bid:r},{reason:e,message:n}))}function a(t,n){e.defaultView&&e.defaultView.frameElement&&(t&&(e.defaultView.frameElement.width=t),n&&(e.defaultView.frameElement.height=n))}const d=(c={resizeFn:a},function(e,t,n){k.hasOwnProperty(e)&&k[e](t,n,c)});var c;function u(t){t.ad?(e.write(t.ad),e.close(),C({doc:e,bid:r,adId:r.adId})):(0,l.H)(r).then((n=>n(t,{sendMessage:(e,t)=>d(e,t,r),mkFrame:i.hw},e.defaultView))).then((()=>C({doc:e,bid:r,adId:r.adId})),(e=>{o(e?.reason||s.as.EXCEPTION,e?.message),e?.stack&&(0,i.vV)(e)}));const n=document.createComment(`Creative ${r.creativeId} served by ${r.bidder} Prebid.js Header Bidding`);(0,i._s)(n,e,"html")}try{t&&e?e!==document||(0,i.al)()?w(t).then((i=>{r=i,D({renderFn:u,resizeFn:a,adId:t,options:{clickUrl:n?.clickThrough},bidResponse:i,doc:e})})):o(s.as.PREVENT_WRITING_ON_MAIN_DOCUMENT,"renderAd was prevented from writing to the main document."):o(s.as.MISSING_DOC_OR_ADID,"missing "+(t?"doc":"adId"))}catch(e){o(A,e.message)}}function N(){if(!window.frames[s.IY])if(document.body){const e=(0,i.CA)();e.name=s.IY,document.body.appendChild(e)}else window.requestAnimationFrame(N)}},201:(e,t,n)=>{n.d(t,{U:()=>s});var i=n(7873),r=n(1069);const o=(0,i.m)();function s(e,t){o.adServers=o.adServers||{},o.adServers[e]=o.adServers[e]||{},Object.keys(t).forEach((n=>{o.adServers[e][n]?(0,r.JE)(`Attempting to add an already registered function property ${n} for AdServer ${e}.`):o.adServers[e][n]=t[n]}))}},9759:(e,t,n)=>{function i(e){var t=e;return{callBids:function(){},setBidderCode:function(e){t=e},getBidderCode:function(){return t}}}n.d(t,{A:()=>i})},1445:(e,t,n)=>{n.d(t,{Ay:()=>H,pX:()=>P,K5:()=>q});var i=n(1069),r=n(433),o=n(2449),s=n(7377),a=n(8044),d=n(3272),c=n(9214),u=n(5901);let l={};function f(e,t,n){let i=function(e,t){let n=l[e]=l[e]||{bidders:{}};return t?n.bidders[t]=n.bidders[t]||{}:n}(e,n);return i[t]=(i[t]||0)+1,i[t]}function g(e){return l?.[e]?.requestsCounter||0}function p(e,t){return l?.[e]?.bidders?.[t]?.requestsCounter||0}function h(e,t){return l?.[e]?.bidders?.[t]?.winsCounter||0}var m=n(7934),b=n(6916),y=n(5023),v=n(8969),E=n(6894),A=n(8462),w=n(5569),T=n(5139),I=n(6811),C=n(2604),O=n(6953);const B="pbsBidAdapter",k={CLIENT:"client",SERVER:"server"},S={isAllowed:T.io,redact:O.$p};let R={},D=R.bidderRegistry={},U=R.aliasRegistry={},_=[];d.$W.getConfig("s2sConfig",(e=>{e&&e.s2sConfig&&(_=(0,i.cy)(e.s2sConfig)?e.s2sConfig:[e.s2sConfig])}));var $={};const j=(0,C.ZI)((e=>R.resolveAlias(e)));function q(e){return j(w.tp,B,{[C.XG]:e.configName})}const x=(0,c.A_)("sync",(function(e){let{bidderCode:t,auctionId:n,bidderRequestId:o,adUnits:s,src:a,metrics:d}=e;return s.reduce(((e,s)=>{const c=s.bids.filter((e=>e.bidder===t));return null==t&&0===c.length&&null!=s.s2sBid&&c.push({bidder:null}),e.push(c.reduce(((e,c)=>{const u=null==(c=Object.assign({},c,{ortb2Imp:(0,i.D9)({},s.ortb2Imp,c.ortb2Imp)},(0,i.SH)(s,["nativeParams","nativeOrtbRequest","mediaType","renderer"]))).mediaTypes?s.mediaTypes:c.mediaTypes;return(0,i.wD)(u)?c=Object.assign({},c,{mediaTypes:u}):(0,i.vV)(`mediaTypes is not correctly configured for adunit ${s.code}`),"client"===a&&function(e,t){f(e,"requestsCounter",t)}(s.code,t),e.push(Object.assign({},c,{adUnitCode:s.code,transactionId:s.transactionId,adUnitId:s.adUnitId,sizes:(0,r.A)(u,"banner.sizes")||(0,r.A)(u,"video.playerSize")||[],bidId:c.bid_id||(0,i.s0)(),bidderRequestId:o,auctionId:n,src:a,metrics:d,bidRequestsCount:g(s.code),bidderRequestsCount:p(s.code,c.bidder),bidderWinsCount:h(s.code,c.bidder),deferBilling:!!s.deferBilling})),e}),[])),e}),[]).reduce(i.Bq,[]).filter((e=>""!==e))}),"getBids");const N=(0,c.A_)("sync",(function(e,t){let{getS2SBidders:n=P}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(null==t)return e;{const i=n(t);return e.filter((e=>i.has(e.bidder)))}}),"filterBidsForAdUnit");const W=(0,c.A_)("sync",((e,t)=>e),"setupAdUnitMediaTypes");function P(e){(0,i.cy)(e)||(e=[e]);const t=new Set([null]);return e.filter((e=>e&&e.enabled)).flatMap((e=>e.bidders)).forEach((e=>t.add(e))),t}const V=(0,c.A_)("sync",(function(e,t){let{getS2SBidders:n=P}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=n(t);return(0,i.ZA)(e).reduce(((e,t)=>(e[r.has(t)?k.SERVER:k.CLIENT].push(t),e)),{[k.CLIENT]:[],[k.SERVER]:[]})}),"partitionBidders");function M(e,t){const n=D[e],i=n?.getSpec&&n.getSpec();if(i&&i[t]&&"function"==typeof i[t])return[i,i[t]]}function G(e,t,n,r){try{(0,i.fH)(`Invoking ${e}.${t}`);for(var o=arguments.length,s=new Array(o>4?o-4:0),a=4;a5&&void 0!==arguments[5]?arguments[5]:{},c=arguments.length>6?arguments[6]:void 0;c=(0,E.BO)(c),y.emit(v.qY.BEFORE_REQUEST_BIDS,e),(0,o.nk)(e),e.forEach((e=>{(0,i.Qd)(e.mediaTypes)||(e.mediaTypes={}),e.bids=e.bids.filter((e=>!e.bidder||S.isAllowed(I.uc,j(w.tW,e.bidder)))),f(e.code,"requestsCounter")})),e=W(e,s);let{[k.CLIENT]:l,[k.SERVER]:g}=V(e,_);d.$W.getConfig("bidderSequence")===d.Ov&&(l=(0,i.k4)(l));const p=(0,m.EN)();let h=[];const A=a.global||{},T=a.bidder||{};function C(e,t){const r=S.redact(null!=t?t:j(w.tW,e.bidderCode)),o=Object.freeze(r.ortb2((0,i.D9)({source:{tid:n}},A,T[e.bidderCode])));return e.ortb2=o,e.bids=e.bids.map((e=>(e.ortb2=o,r.bidRequest(e)))),e}_.forEach((r=>{const o=q(r);if(r&&r.enabled&&S.isAllowed(I.uc,o)){let{adUnits:s,hasModuleBids:a}=function(e,t){let n=(0,i.Go)(e),r=!1;return n.forEach((e=>{const n=e.bids.filter((e=>e.module===B&&e.params?.configName===t.configName));1===n.length?(e.s2sBid=n[0],r=!0,e.ortb2Imp=(0,i.D9)({},e.s2sBid.ortb2Imp,e.ortb2Imp)):n.length>1&&(0,i.JE)('Multiple "module" bids for the same s2s configuration; all will be ignored',n),e.bids=N(e.bids,t).map((e=>(e.bid_id=(0,i.s0)(),e)))})),n=n.filter((e=>0!==e.bids.length||null!=e.s2sBid)),{adUnits:n,hasModuleBids:r}}(e,r),d=(0,i.lk)();(0===g.length&&a?[null]:g).forEach((e=>{const a=(0,i.s0)(),u=c.fork(),l=C({bidderCode:e,auctionId:n,bidderRequestId:a,uniquePbsTid:d,bids:x({bidderCode:e,auctionId:n,bidderRequestId:a,adUnits:(0,i.Go)(s),src:v.RW.SRC,metrics:u}),auctionStart:t,timeout:r.timeout,src:v.RW.SRC,refererInfo:p,metrics:u},o);0!==l.bids.length&&h.push(l)})),s.forEach((e=>{let t=e.bids.filter((e=>(0,u.I6)(h,(t=>(0,u.I6)(t.bids,(t=>t.bidId===e.bid_id))))));e.bids=t})),h.forEach((e=>{void 0===e.adUnitsS2SCopy&&(e.adUnitsS2SCopy=s.filter((e=>e.bids.length>0||null!=e.s2sBid)))}))}}));let O=function(e){let t=(0,i.Go)(e);return t.forEach((e=>{e.bids=N(e.bids,null)})),t=t.filter((e=>0!==e.bids.length)),t}(e);return l.forEach((e=>{const o=(0,i.s0)(),a=c.fork(),d=C({bidderCode:e,auctionId:n,bidderRequestId:o,bids:x({bidderCode:e,auctionId:n,bidderRequestId:o,adUnits:(0,i.Go)(O),labels:s,src:"client",metrics:a}),auctionStart:t,timeout:r,refererInfo:p,metrics:a}),u=D[e];u||(0,i.vV)(`Trying to make a request for bidder that does not exist: ${e}`),u&&d.bids&&0!==d.bids.length&&h.push(d)})),h.forEach((e=>{b.mW.getConsentData()&&(e.gdprConsent=b.mW.getConsentData()),b.t6.getConsentData()&&(e.uspConsent=b.t6.getConsentData()),b.ad.getConsentData()&&(e.gppConsent=b.ad.getConsentData())})),h}),"makeBidRequests"),R.callBids=function(e,t,n,r,o,s,c){let u=arguments.length>7&&void 0!==arguments[7]?arguments[7]:{};if(!t.length)return void(0,i.JE)("callBids executed with no bidRequests. Were they filtered by labels or sizing?");let[l,f]=t.reduce(((e,t)=>(e[Number(void 0!==t.src&&t.src===v.RW.SRC)].push(t),e)),[[],[]]);var g=[];f.forEach((e=>{for(var t=-1,n=0;n{if(e&&g[p]&&P(e).has(g[p].bidderCode)){const t=(0,a.g4)(s,o?{request:o.request.bind(null,"s2s"),done:o.done}:void 0);let d=e.bidders;const l=D[e.adapter];let h=g[p].uniquePbsTid,m=g[p].adUnitsS2SCopy,b=f.filter((e=>e.uniquePbsTid===h));if(l){let o={ad_units:m,s2sConfig:e,ortb2Fragments:u,requestBidsTimeout:s};if(o.ad_units.length){let e=b.map((e=>(e.start=(0,i.vE)(),function(t){t||c(e.bidderRequestId),r.apply(e,arguments)})));const s=(0,i.ZA)(o.ad_units).filter((e=>d.includes(e)));(0,i.OG)(`CALLING S2S HEADER BIDDERS ==== ${s.length>0?s.join(", "):'No bidder specified, using "ortb2Imp" definition(s) only'}`),b.forEach((e=>{y.emit(v.qY.BID_REQUESTED,{...e,tid:e.auctionId})})),l.callBids(o,f,n,(t=>e.forEach((e=>e(t)))),t)}}else(0,i.vV)("missing "+e.adapter);p++}})),l.forEach((e=>{e.start=(0,i.vE)();const t=D[e.bidderCode];d.$W.runWithBidder(e.bidderCode,(()=>{(0,i.OG)("CALLING BIDDER"),y.emit(v.qY.BID_REQUESTED,e)}));let u=(0,a.g4)(s,o?{request:o.request.bind(null,e.bidderCode),done:o.done}:void 0);const l=r.bind(e);try{d.$W.runWithBidder(e.bidderCode,t.callBids.bind(t,e,n,l,u,(()=>c(e.bidderRequestId)),d.$W.callbackWithBidder(e.bidderCode)))}catch(t){(0,i.vV)(`${e.bidderCode} Bid Adapter emitted an uncaught error when parsing their bidRequest`,{e:t,bidRequest:e}),l()}}))},R.videoAdapters=[],R.registerBidAdapter=function(e,t){let{supportedMediaTypes:n=[]}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};e&&t?"function"==typeof e.callBids?(D[t]=e,b.o2.register(w.tW,t,e.getSpec?.().gvlid),(0,u.mK)(n,"video")&&R.videoAdapters.push(t),(0,u.mK)(n,"native")&&o.mT.push(t)):(0,i.vV)("Bidder adaptor error for bidder code: "+t+"bidder must implement a callBids() function"):(0,i.vV)("bidAdapter or bidderCode not specified")},R.aliasBidAdapter=function(e,t,n){if(void 0===D[t]){let r=D[e];if(void 0===r){const n=[];_.forEach((i=>{if(i.bidders&&i.bidders.length){const r=i&&i.bidders;i&&(0,u.mK)(r,t)?U[t]=e:n.push(e)}})),n.forEach((e=>{(0,i.vV)('bidderCode "'+e+'" is not an existing bidder.',"adapterManager.aliasBidAdapter")}))}else try{let a,d=function(e){let t=[];return(0,u.mK)(R.videoAdapters,e)&&t.push("video"),(0,u.mK)(o.mT,e)&&t.push("native"),t}(e);if(r.constructor.prototype!=Object.prototype)a=new r.constructor,a.setBidderCode(t);else{const{useBaseGvlid:o=!1}=n||{};let d=r.getSpec();const c=o?d.gvlid:n?.gvlid;null==c&&null!=d.gvlid&&(0,i.JE)(`Alias '${t}' will NOT re-use the GVL ID of the original adapter ('${d.code}', gvlid: ${d.gvlid}). Functionality that requires TCF consent may not work as expected.`);let u=n&&n.skipPbsAliasing;a=(0,s.xb)(Object.assign({},d,{code:t,gvlid:c,skipPbsAliasing:u})),U[t]=e}R.registerBidAdapter(a,t,{supportedMediaTypes:d})}catch(t){(0,i.vV)(e+" bidder does not currently support aliasing.","adapterManager.aliasBidAdapter")}}else(0,i.OG)('alias name "'+t+'" has been already specified.')},R.resolveAlias=function(e){let t,n=e;for(;U[n]&&(!t||!t.has(n));)n=U[n],(t=t||new Set).add(n);return n},R.registerAnalyticsAdapter=function(e){let{adapter:t,code:n,gvlid:r}=e;t&&n?"function"==typeof t.enableAnalytics?(t.code=n,$[n]={adapter:t,gvlid:r},b.o2.register(w.Tn,n,r)):(0,i.vV)(`Prebid Error: Analytics adaptor error for analytics "${n}"\n analytics adapter must implement an enableAnalytics() function`):(0,i.vV)("Prebid Error: analyticsAdapter or analyticsCode not specified")},R.enableAnalytics=function(e){(0,i.cy)(e)||(e=[e]),e.forEach((e=>{const t=$[e.provider];t&&t.adapter?S.isAllowed(I.mo,j(w.Tn,e.provider,{[C.TQ]:e}))&&t.adapter.enableAnalytics(e):(0,i.vV)(`Prebid Error: no analytics adapter found in registry for '${e.provider}'.`)}))},R.getBidAdapter=function(e){return D[e]},R.getAnalyticsAdapter=function(e){return $[e]},R.callTimedOutBidders=function(e,t,n){t=t.map((t=>(t.params=(0,i.SB)(e,t.adUnitCode,t.bidder),t.timeout=n,t))),t=(0,i.$z)(t,"bidder"),Object.keys(t).forEach((e=>{F(e,"onTimeout",t[e])}))},R.callBidWonBidder=function(e,t,n){var r,o;t.params=(0,i.SB)(n,t.adUnitCode,t.bidder),r=t.adUnitCode,o=t.bidder,f(r,"winsCounter",o),F(e,"onBidWon",t)},R.triggerBilling=(()=>{const e=new WeakSet;return t=>{e.has(t)||(e.add(t),t.source===v.RW.SRC&&t.burl&&i.mM.triggerPixel(t.burl),F(t.bidder,"onBidBillable",t))}})(),R.callSetTargetingBidder=function(e,t){F(e,"onSetTargeting",t)},R.callBidViewableBidder=function(e,t){F(e,"onBidViewable",t)},R.callBidderError=function(e,t,n){F(e,"onBidderError",{error:t,bidderRequest:n})},R.callAdRenderSucceededBidder=function(e,t){F(e,"onAdRenderSucceeded",t)},R.callDataDeletionRequest=(0,c.A_)("sync",(function(){for(var e=arguments.length,t=new Array(e),n=0;n!U.hasOwnProperty(e))).forEach((e=>{const n=M(e,r);if(null!=n){const i=A.n.getBidsRequested().filter((t=>function(e){const t=new Set;for(;U.hasOwnProperty(e)&&!t.has(e);)t.add(e),e=U[e];return e}(t.bidderCode)===e));G(e,r,...n,i,...t)}})),Object.entries($).forEach((e=>{let[n,o]=e;const s=o?.adapter?.[r];if("function"==typeof s)try{s.apply(o.adapter,t)}catch(e){(0,i.vV)(`error calling ${r} of ${n}`,e)}}))}));const H=R},7377:(e,t,n)=>{n.d(t,{JN:()=>S,a$:()=>I,fn:()=>R,xb:()=>C});var i=n(9759),r=n(1445),o=n(3272),s=n(3597),a=n(8230),d=n(2449),c=n(3895),u=n(8969),l=n(5023),f=n(5901),g=n(1069),p=n(9214),h=n(8462),m=n(2693),b=n(6894),y=n(5139),v=n(3441),E=n(5569),A=n(6811);const w=["cpm","ttl","creativeId","netRevenue","currency"],T=["auctionId","transactionId"];function I(e){const t=Array.isArray(e.supportedMediaTypes)?{supportedMediaTypes:e.supportedMediaTypes}:void 0;function n(e){const n=C(e);r.Ay.registerBidAdapter(n,e.code,t)}n(e),Array.isArray(e.aliases)&&e.aliases.forEach((t=>{let i,o,s=t;(0,g.Qd)(t)&&(s=t.code,i=t.gvlid,o=t.skipPbsAliasing),r.Ay.aliasRegistry[s]=e.code,n(Object.assign({},e,{code:s,gvlid:i,skipPbsAliasing:o}))}))}function C(e){return Object.assign(new i.A(e.code),{getSpec:function(){return Object.freeze(Object.assign({},e))},registerSyncs:t,callBids:function(n,i,a,d,c,f){if(!Array.isArray(n.bids))return;const p=function(e){if((0,y.io)(A.VJ,(0,v.s)(E.tW,e)))return{bidRequest:e=>e,bidderRequest:e=>e};function t(e,t,n){return T.includes(t)?null:Reflect.get(e,t,n)}function n(e,t){const n=new Proxy(e,t);return Object.entries(e).filter((e=>{let[t,n]=e;return"function"==typeof n})).forEach((t=>{let[i,r]=t;return n[i]=r.bind(e)})),n}const i=(0,g.Bj)((e=>n(e,{get:t})),(e=>e.bidId));return{bidRequest:i,bidderRequest:e=>n(e,{get:(n,r,o)=>"bids"===r?e.bids.map(i):t(n,r,o)})}}(n.bidderCode),h={};const w=[];function I(){a(),o.$W.runWithBidder(e.code,(()=>{l.emit(u.qY.BIDDER_DONE,n),t(w,n.gdprConsent,n.uspConsent,n.gppConsent)}))}const C=D(n).measureTime("validate",(()=>n.bids.filter((t=>function(t){if(!e.isBidRequestValid(t))return(0,g.JE)(`Invalid bid sent to bidder ${e.code}: ${JSON.stringify(t)}`),!1;return!0}(p.bidRequest(t))))));if(0===C.length)return void I();const O={};C.forEach((e=>{O[e.bidId]=e,e.adUnitCode||(e.adUnitCode=e.placementCode)})),B(e,C.map(p.bidRequest),p.bidderRequest(n),d,f,{onRequest:e=>l.emit(u.qY.BEFORE_BIDDER_HTTP,n,e),onResponse:t=>{c(e.code),w.push(t)},onPaapi:e=>{const t=O[e.bidId];t?S(t,e):(0,g.JE)("Received fledge auction configuration for an unknown bidId",e)},onError:(t,i)=>{i.timedOut||c(e.code),r.Ay.callBidderError(e.code,i,n),l.emit(u.qY.BIDDER_ERROR,{error:i,bidderRequest:n}),(0,g.vV)(`Server call for ${e.code} failed: ${t} ${i.status}. Continuing without bids.`)},onBid:t=>{const n=O[t.requestId];if(n){if(t.adapterCode=n.bidder,function(e,t){let n=m.u.get(t,"allowAlternateBidderCodes")||!1,i=m.u.get(t,"allowedAlternateBidderCodes");if(e&&t&&t!==e&&(i=(0,g.cy)(i)?i.map((e=>e.trim().toLowerCase())).filter((e=>!!e)).filter(g.hj):i,!n||(0,g.cy)(i)&&"*"!==i[0]&&!i.includes(e)))return!0;return!1}(t.bidderCode,n.bidder))return(0,g.JE)(`${t.bidderCode} is not a registered partner or known bidder of ${n.bidder}, hence continuing without bid. If you wish to support this bidder, please mark allowAlternateBidderCodes as true in bidderSettings.`),void i.reject(n.adUnitCode,t,u.Tf.BIDDER_DISALLOWED);t.originalCpm=t.cpm,t.originalCurrency=t.currency,t.meta=t.meta||Object.assign({},t[n.bidder]),t.deferBilling=n.deferBilling,t.deferRendering=t.deferBilling&&(t.deferRendering??"function"!=typeof e.onBidBillable);const r=Object.assign((0,s.O)(u.XQ.GOOD,n),t,(0,g.Up)(n,T));!function(e,t){const n=(0,b.BO)(t.metrics);n.checkpoint("addBidResponse"),h[e]=!0,n.measureTime("addBidResponse.validate",(()=>R(e,t)))?i(e,t):i.reject(e,t,u.Tf.INVALID)}(n.adUnitCode,r)}else(0,g.JE)(`Bidder ${e.code} made bid for unknown request ID: ${t.requestId}. Ignoring.`),i.reject(null,t,u.Tf.INVALID_REQUEST_ID)},onCompletion:I})}});function t(t,n,i,r){k(e,t,n,i,r)}}const O=["bids","paapi"],B=(0,p.A_)("sync",(function(e,t,n,i,r,o){let{onRequest:s,onResponse:a,onPaapi:d,onError:c,onBid:u,onCompletion:l}=o;const f=D(n);l=f.startTiming("total").stopBefore(l);let p=f.measureTime("buildRequests",(()=>e.buildRequests(t,n)));if(!p||0===p.length)return void l();Array.isArray(p)||(p=[p]);const h=(0,g.U6)(l,p.length);p.forEach((t=>{const n=f.fork();function o(e){null!=e&&(e.metrics=n.fork().renameWith()),u(e)}const l=r((function(i,r){b();try{i=JSON.parse(i)}catch(e){}i={body:i,headers:{get:r.getResponseHeader.bind(r)}},a(i);try{i=n.measureTime("interpretResponse",(()=>e.interpretResponse(i,t)))}catch(t){return(0,g.vV)(`Bidder ${e.code} failed to interpret the server's response. Continuing without bids`,null,t),void h()}let s,c;i&&!Object.keys(i).some((e=>!O.includes(e)))?(s=i.bids,c=i.paapi):s=i,(0,g.cy)(c)&&c.forEach(d),s&&((0,g.cy)(s)?s.forEach(o):o(s)),h()})),p=r((function(e,t){b(),c(e,t),h()}));s(t);const b=n.startTiming("net");function w(n){const i=t.options;return Object.assign(n,i,{browsingTopics:!(i?.hasOwnProperty("browsingTopics")&&!i.browsingTopics)&&((m.u.get(e.code,"topicsHeader")??!0)&&(0,y.io)(A.DL,(0,v.s)(E.tW,e.code)))})}switch(t.method){case"GET":i(`${t.url}${function(e){if(e)return`?${"object"==typeof e?(0,g.bL)(e):e}`;return""}(t.data)}`,{success:l,error:p},void 0,w({method:"GET",withCredentials:!0}));break;case"POST":i(t.url,{success:l,error:p},"string"==typeof t.data?t.data:JSON.stringify(t.data),w({method:"POST",contentType:"text/plain",withCredentials:!0}));break;default:(0,g.JE)(`Skipping invalid request from ${e.code}. Request type ${t.type} must be GET or POST`),h()}}))}),"processBidderRequests"),k=(0,p.A_)("async",(function(e,t,n,i,s){const d=o.$W.getConfig("userSync.aliasSyncEnabled");if(e.getUserSyncs&&(d||!r.Ay.aliasRegistry[e.code])){let r=o.$W.getConfig("userSync.filterSettings"),d=e.getUserSyncs({iframeEnabled:!(!r||!r.iframe&&!r.all),pixelEnabled:!(!r||!r.image&&!r.all)},t,n,i,s);d&&(Array.isArray(d)||(d=[d]),d.forEach((t=>{a.zt.registerSync(t.type,e.code,t.url)})),a.zt.bidderDone(e.code))}}),"registerSyncs"),S=(0,p.A_)("sync",((e,t)=>{}),"addPaapiConfig");function R(e,t){let{index:n=h.n.index}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};function i(e){return`Invalid bid from ${t.bidderCode}. Ignoring bid: ${e}`}return e?t?function(){let e=Object.keys(t);return w.every((n=>(0,f.mK)(e,n)&&!(0,f.mK)([void 0,null],t[n])))}()?"native"!==t.mediaType||(0,d.Bm)(t,{index:n})?"video"!==t.mediaType||(0,c.vk)(t,{index:n})?!("banner"===t.mediaType&&!function(e,t){let{index:n=h.n.index}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if((t.width||0===parseInt(t.width,10))&&(t.height||0===parseInt(t.height,10)))return t.width=parseInt(t.width,10),t.height=parseInt(t.height,10),!0;const i=n.getBidRequest(t),r=n.getMediaTypes(t),o=i&&i.sizes||r&&r.banner&&r.banner.sizes,s=(0,g.kK)(o);if(1===s.length){const[e,n]=s[0].split("x");return t.width=parseInt(e,10),t.height=parseInt(n,10),!0}return!1}(e,t,{index:n}))||((0,g.vV)(i("Banner bids require a width and height")),!1):((0,g.vV)(i("Video bid does not have required vastUrl or renderer property")),!1):((0,g.vV)(i("Native bid missing some required properties.")),!1):((0,g.vV)(i(`Bidder ${t.bidderCode} is missing required params. Check http://prebid.org/dev-docs/bidder-adapter-1.html for list of params.`)),!1):((0,g.JE)(`Some adapter tried to add an undefined bid for ${e}.`),!1):((0,g.JE)("No adUnitCode was supplied to addBidResponse."),!1)}function D(e){return(0,b.BO)(e.metrics).renameWith((t=>[`adapter.client.${t}`,`adapters.client.${e.bidderCode}.${t}`]))}},1580:(e,t,n)=>{n.d(t,{R:()=>a});var i=n(5901),r=n(1069);const o=new WeakMap,s=["debugging","outstream","improvedigital","showheroes-bs","aaxBlockmeter","adagio","adloox","akamaidap","arcspan","airgrid","browsi","brandmetrics","clean.io","humansecurity","confiant","contxtful","hadron","mediafilter","medianet","azerionedge","a1Media","geoedge","qortex","dynamicAdBoost","51Degrees","symitridap","wurfl","justtag","tncId","ftrackId","id5"];function a(e,t,n,a,d){if(!t||!e)return void(0,r.vV)("cannot load external script without url and moduleCode");if(!(0,i.mK)(s,t))return void(0,r.vV)(`${t} not whitelisted for loading external JavaScript`);a||(a=document);const c=f(a,e);if(c)return n&&"function"==typeof n&&(c.loaded?n():c.callbacks.push(n)),c.tag;const u=o.get(a)||{},l={loaded:!1,tag:null,callbacks:[]};return u[e]=l,o.set(a,u),n&&"function"==typeof n&&l.callbacks.push(n),(0,r.JE)(`module ${t} is loading external JavaScript`),function(t,n,i,o){i||(i=document);var s=i.createElement("script");s.type="text/javascript",s.async=!0;const a=f(i,e);a&&(a.tag=s);s.readyState?s.onreadystatechange=function(){"loaded"!==s.readyState&&"complete"!==s.readyState||(s.onreadystatechange=null,n())}:s.onload=function(){n()};s.src=t,o&&(0,r.Bg)(s,o);return(0,r._s)(s,i),s}(e,(function(){l.loaded=!0;try{for(let e=0;e{n.d(t,{Q:()=>i});const i=(0,n(9214).A_)("sync",(()=>{}))},8044:(e,t,n)=>{n.d(t,{RD:()=>f,g4:()=>l,hd:()=>g});var i=n(3272),r=n(1069);const o={fetch:window.fetch.bind(window),makeRequest:(e,t)=>new Request(e,t),timeout(e,t){const n=new AbortController;let i=setTimeout((()=>{n.abort(),(0,r.vV)(`Request timeout after ${e}ms`,t),i=null}),e);return{signal:n.signal,done(){i&&clearTimeout(i)}}}},s="GET",a="POST",d="Content-Type";function c(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:3e3,{request:t,done:n}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},r=(t,n)=>{let r;null==e||null!=n?.signal||i.$W.getConfig("disableAjaxTimeout")||(r=o.timeout(e,t),n=Object.assign({signal:r.signal},n));let s=o.fetch(t,n);return null!=r?.done&&(s=s.finally(r.done)),s};return null==t&&null==n||(r=(e=>function(i,r){const o=new URL(null==i?.url?i:i.url,document.location).origin;let s=e(i,r);return t&&t(o),n&&(s=s.finally((()=>n(o)))),s})(r)),r}function u(e,t){let{status:n,statusText:i="",headers:o,url:s}=e,a=0;function c(e){if(0===a)try{a=(new DOMParser).parseFromString(t,o?.get(d)?.split(";")?.[0])}catch(t){a=null,e&&e(t)}return a}return{readyState:XMLHttpRequest.DONE,status:n,statusText:i,responseText:t,response:t,responseType:"",responseURL:s,get responseXML(){return c(r.vV)},getResponseHeader:e=>o?.has(e)?o.get(e):null,toJSON(){return Object.assign({responseXML:c()},this)},timedOut:!1}}function l(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:3e3,{request:t,done:n}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const i=c(e,{request:t,done:n});return function(e,t,n){!function(e,t){const{success:n,error:i}="object"==typeof t&&null!=t?t:{success:"function"==typeof t?t:()=>null,error:(e,t)=>(0,r.vV)("Network error",e,t)};e.then((e=>e.text().then((t=>[e,t])))).then((e=>{let[t,r]=e;const o=u(t,r);t.ok||304===t.status?n(r,o):i(t.statusText,o)}),(e=>i("",Object.assign(u({status:0},""),{reason:e,timedOut:"AbortError"===e?.name}))))}(i(function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const i=n.method||(t?a:s);if(i===s&&t){const i=(0,r.Dl)(e,n);Object.assign(i.search,t),e=(0,r.c$)(i)}const c=new Headers(n.customHeaders);c.set(d,n.contentType||"text/plain");const u={method:i,headers:c};return i!==s&&t&&(u.body=t),n.withCredentials&&(u.credentials="include"),n.browsingTopics&&isSecureContext&&(u.browsingTopics=!0),n.keepalive&&(u.keepalive=!0),o.makeRequest(e,u)}(e,n,arguments.length>3&&void 0!==arguments[3]?arguments[3]:{})),t)}}const f=l(),g=c()},8462:(e,t,n)=>{n.d(t,{n:()=>se});var i=n(1069),r=n(433),o=n(6833),s=n(2449),a=n(8044),d=n(3272);const c=15;function u(e){let{index:t=se.index}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=e.vastXml?e.vastXml:(i=e.vastUrl,r=e.vastImpUrl,`\n \n \n prebid.org wrapper\n \n ${(r=r&&(Array.isArray(r)?r:[r]))?r.map((e=>``)).join(""):""}\n \n \n \n `);var i,r;const o=t.getAuction(e);let s={type:"xml",value:n,ttlseconds:Number(e.ttl)+c};return d.$W.getConfig("cache.vasttrack")&&(s.bidder=e.bidder,s.bidid=e.requestId,s.aid=e.auctionId),null!=o&&(s.timestamp=o.getAuctionStart()),"string"==typeof e.customCacheKey&&""!==e.customCacheKey&&(s.key=e.customCacheKey),s}const l={store:function(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:a.g4;const i={puts:e.map(u)};n(d.$W.getConfig("cache.timeout"))(d.$W.getConfig("cache.url"),function(e){return{success:function(t){let n;try{n=JSON.parse(t).responses}catch(t){return void e(t,[])}n?e(null,n):e(new Error("The cache server didn't respond with a responses property."),[])},error:function(t,n){e(new Error(`Error storing video ad in the cache: ${t}: ${JSON.stringify(n)}`),[])}}}(t),JSON.stringify(i),{contentType:"text/plain",withCredentials:!0})}};function f(e){const t=e.map((e=>e.bidResponse));l.store(t,(function(n,r){var o;n?(o=n,(0,i.vV)(`Failed to save to the video cache: ${o}. Video bids will be discarded:`,t)):e.length!==r.length?(0,i.vV)(`expected ${e.length} cache IDs, got ${r.length} instead`):r.forEach(((t,n)=>{const{auctionInstance:r,bidResponse:o,afterBidAdded:s}=e[n];var a;""===t.uuid?(0,i.JE)("Supplied video cache key was already in use by Prebid Cache; caching attempt was rejected. Video bid must be discarded."):(o.videoCacheKey=t.uuid,o.vastUrl||(o.vastUrl=(a=o.videoCacheKey,`${d.$W.getConfig("cache.url")}?uuid=${a}`)),F(r,o),s())}))}))}let g,p;d.$W.getConfig("cache",(e=>{g="number"==typeof e.cache.batchSize&&e.cache.batchSize>0?e.cache.batchSize:1,p="number"==typeof e.cache.batchTimeout&&e.cache.batchTimeout>0?e.cache.batchTimeout:0}));const h=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:setTimeout,t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:f,n=[[]],i=!1;const r=e=>e();return function(o,s,a){const d=p>0?e:r;n[n.length-1].length>=g&&n.push([]),n[n.length-1].push({auctionInstance:o,bidResponse:s,afterBidAdded:a}),i||(i=!0,d((()=>{n.forEach(t),n=[[]],i=!1}),p))}}();var m=n(5789),b=n(8230),y=n(9214),v=n(5901),E=n(3895),A=n(1371),w=n(2693),T=n(5023),I=n(1445),C=n(8969),O=n(5555),B=n(6894),k=n(7176),S=n(7873);const{syncUsers:R}=b.zt,D="started",U="inProgress",_="completed";T.on(C.qY.BID_ADJUSTMENT,(function(e){!function(e){let t=(0,k.y)(e.cpm,e);t>=0&&(e.cpm=t)}(e)}));const $=4,j={},q={},x=[],N=(0,S.m)();function W(e){let{adUnits:t,adUnitCodes:n,callback:a,cbTimeout:c,labels:u,auctionId:l,ortb2Fragments:f,metrics:g}=e;g=(0,B.BO)(g);const p=t,h=u,b=n,y=l||(0,i.lk)(),w=c,k=new Set,S=(0,O.v)();let W,z,J,K,Q=[],Y=a,X=[],Z=[],ee=[],ne=[],ie=[];function re(){return{auctionId:y,timestamp:W,auctionEnd:z,auctionStatus:K,adUnits:p,adUnitCodes:b,labels:h,bidderRequests:X,noBids:ee,bidsReceived:Z,bidsRejected:Q,winningBids:ne,timeout:w,metrics:g,seatNonBids:ie}}function oe(e){if(e?T.emit(C.qY.AUCTION_TIMEOUT,re()):clearTimeout(J),void 0===z){let n=[];e&&((0,i.OG)(`Auction ${y} timedOut`),n=X.filter((e=>!k.has(e.bidderRequestId))).flatMap((e=>e.bids)),n.length&&T.emit(C.qY.BID_TIMEOUT,n)),K=_,z=Date.now(),g.checkpoint("auctionEnd"),g.timeBetween("requestBids","auctionEnd","requestBids.total"),g.timeBetween("callBids","auctionEnd","requestBids.callBids"),S.resolve(),T.emit(C.qY.AUCTION_END,re()),G(p,(function(){try{if(null!=Y){const t=Z.filter((e=>b.includes(e.adUnitCode))).reduce(te,{});Y.apply(N,[t,e,y]),Y=null}}catch(e){(0,i.vV)("Error executing bidsBackHandler",null,e)}finally{n.length&&I.Ay.callTimedOutBidders(t,n,w);let e=d.$W.getConfig("userSync")||{};e.enableOverride||R(e.syncDelay)}}))}}function ae(){d.$W.resetBidder(),(0,i.fH)(`Bids Received for Auction with id: ${y}`,Z),K=_,oe(!1)}function de(e){k.add(e)}function ce(e){e.forEach((e=>{var t;t=e,X=X.concat(t)}));let t={},n={bidRequests:e,run:()=>{J=setTimeout((()=>oe(!0)),w),K=U,T.emit(C.qY.AUCTION_INIT,re());let n=function(e,t){let{index:n=se.index}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},a=0,c=!1,u=new Set,l={};function f(){a--,c&&0===a&&e()}function g(e,t,n){return l[t.requestId]=!0,function(e,t){let{index:n=se.index}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};const r=n.getBidderRequest(e),o=n.getAdUnit(e),s=r&&r.start||e.requestTimestamp;Object.assign(e,{responseTimestamp:e.responseTimestamp||(0,i.vE)(),requestTimestamp:e.requestTimestamp||s,cpm:parseFloat(e.cpm)||0,bidder:e.bidder||e.bidderCode,adUnitCode:t}),null!=o?.ttlBuffer&&(e.ttlBuffer=o.ttlBuffer);e.timeToRespond=e.responseTimestamp-e.requestTimestamp}(t,e),a++,n(f)}function p(e,a){g(e,a,(e=>{let c=function(e){let{index:t=se.index}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};T.emit(C.qY.BID_ADJUSTMENT,e);const n=t.getBidRequest(e)?.renderer||t.getAdUnit(e).renderer,i=e.mediaType,r=t.getMediaTypes(e),s=r&&r[i];var a=s&&s.renderer,c=null;a&&a.url&&a.render&&(!0!==a.backupOnly||!e.renderer)?c=a:n&&n.url&&n.render&&(!0!==n.backupOnly||!e.renderer)&&(c=n);c&&(e.renderer=m.A4.install({url:c.url,config:c.options}),e.renderer.setRender(c.render));const u=L(e.mediaType,r,d.$W.getConfig("mediaTypePriceGranularity")),l=(0,o.j)(e.cpm,"object"==typeof u?u:d.$W.getConfig("customPriceBucket"),d.$W.getConfig("currency.granularityMultiplier"));return e.pbLg=l.low,e.pbMg=l.med,e.pbHg=l.high,e.pbAg=l.auto,e.pbDg=l.dense,e.pbCg=l.custom,e}(a);T.emit(C.qY.BID_ACCEPTED,c),c.mediaType===A.G_?function(e,t,n){let{index:o=se.index}=arguments.length>3&&void 0!==arguments[3]?arguments[3]:{},s=!0;const a=(0,r.A)(o.getMediaTypes({requestId:t.originalRequestId||t.requestId,adUnitId:t.adUnitId}),"video"),c=a&&(0,r.A)(a,"context"),u=a&&(0,r.A)(a,"useCacheKey");d.$W.getConfig("cache.url")&&(u||c!==E.H6)&&(!t.videoCacheKey||d.$W.getConfig("cache.ignoreBidderCacheKey")?(s=!1,H(e,t,n,a)):t.vastUrl||((0,i.vV)("videoCacheKey specified but not required vastUrl for video bid"),s=!1));s&&(F(e,t),n())}(t,c,e):((0,s.l6)(c)&&(0,s.gs)(c,n.getAdUnit(c)),F(t,c),e())}))}function h(e,n,r){return g(e,n,(e=>{n.rejectionReason=r,(0,i.JE)(`Bid from ${n.bidder||"unknown bidder"} was rejected: ${r}`,n),T.emit(C.qY.BID_REJECTED,n),t.addBidRejected(n),e()}))}function b(){let n=this,r=t.getBidRequests();const o=d.$W.getConfig("auctionOptions");if(u.add(n),o&&!(0,i.Im)(o)){const e=o.secondaryBidders;e&&!r.every((t=>(0,v.mK)(e,t.bidderCode)))&&(r=r.filter((t=>!(0,v.mK)(e,t.bidderCode))))}c=r.every((e=>u.has(e))),n.bids.forEach((e=>{l[e.bidId]||(t.addNoBid(e),T.emit(C.qY.NO_BID,e))})),c&&0===a&&e()}return{addBidResponse:function(){function e(e,t){P.call({dispatch:p},e,t,(()=>{let n=!1;return i=>{n||(h(e,t,i),n=!0)}})())}return e.reject=h,e}(),adapterDone:function(){V(O.k.resolve()).finally((()=>b.call(this)))}}}(ae,this);I.Ay.callBids(p,e,n.addBidResponse,n.adapterDone,{request(e,n){c(j,n),c(t,e),q[e]||(q[e]={SRA:!0,origin:n}),t[e]>1&&(q[e].SRA=!1)},done(e){j[e]--,x[0]&&a(x[0])&&x.shift()}},w,de,f)}};function a(e){let t=!0,n=d.$W.getConfig("maxRequestsPerOrigin")||$;return e.bidRequests.some((e=>{let i=1,r=void 0!==e.src&&e.src===C.RW.SRC?"s2s":e.bidderCode;return q[r]&&(!1===q[r].SRA&&(i=Math.min(e.bids.length,n)),j[q[r].origin]+i>n&&(t=!1)),!t})),t&&e.run(),t}function c(e,t){void 0===e[t]?e[t]=1:e[t]++}a(n)||((0,i.JE)("queueing auction due to limited endpoint capacity"),x.push(n))}return T.on(C.qY.SEAT_NON_BID,(e=>{var t;e.auctionId===y&&(t=e.seatnonbid,ie=ie.concat(t))})),{addBidReceived:function(e){Z=Z.concat(e)},addBidRejected:function(e){Q=Q.concat(e)},addNoBid:function(e){ee=ee.concat(e)},callBids:function(){K=D,W=Date.now();let e=g.measureTime("requestBids.makeRequests",(()=>I.Ay.makeBidRequests(p,W,y,w,h,f,g)));(0,i.fH)(`Bids Requested for Auction with id: ${y}`,e),g.checkpoint("callBids"),e.length<1?((0,i.JE)("No valid bid requests returned for auction"),ae()):M.call({dispatch:ce,context:this},e)},addWinningBid:function(e){ne=ne.concat(e),I.Ay.callBidWonBidder(e.adapterCode||e.bidder,e,t),e.deferBilling||I.Ay.triggerBilling(e)},setBidTargeting:function(e){I.Ay.callSetTargetingBidder(e.adapterCode||e.bidder,e)},getWinningBids:()=>ne,getAuctionStart:()=>W,getAuctionEnd:()=>z,getTimeout:()=>w,getAuctionId:()=>y,getAuctionStatus:()=>K,getAdUnits:()=>p,getAdUnitCodes:()=>b,getBidRequests:()=>X,getBidsReceived:()=>Z,getNoBids:()=>ee,getNonBids:()=>ie,getFPD:()=>f,getMetrics:()=>g,end:S.promise}}const P=(0,y.A_)("sync",(function(e,t,n){!function(e){const t=d.$W.getConfig("maxBid");return!t||!e.cpm||t>=Number(e.cpm)}(t)?n(C.Tf.PRICE_TOO_HIGH):this.dispatch.call(null,e,t)}),"addBidResponse"),V=(0,y.A_)("sync",(e=>e),"responsesReady"),M=(0,y.A_)("sync",(function(e){this.dispatch.call(this.context,e)}),"addBidderRequests"),G=(0,y.A_)("async",(function(e,t){t&&t()}),"bidsBackCallback");function F(e,t){!function(e){let t;const n=!0===w.u.get(e.bidderCode,"allowZeroCpmBids")?e.cpm>=0:e.cpm>0;e.bidderCode&&(n||e.dealId)&&(t=function(e,t){let{index:n=se.index}=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{};if(!t)return{};const i=n.getBidRequest(t);var r={};const o=Z(t.mediaType,e);ee(r,o,t,i),e&&w.u.getOwn(e,C.iD.ADSERVER_TARGETING)&&(ee(r,w.u.ownSettingsFor(e),t,i),t.sendStandardTargeting=w.u.get(e,"sendStandardTargeting"));t.native&&(r=Object.assign({},r,(0,s.Zj)(t)));return r}(e.bidderCode,e));e.adserverTargeting=Object.assign(e.adserverTargeting||{},t)}(t),(0,B.BO)(t.metrics).timeSince("addBidResponse","addBidResponse.total"),e.addBidReceived(t),T.emit(C.qY.BID_RESPONSE,t)}const H=(0,y.A_)("async",(function(e,t,n,i){h(e,t,n)}),"callPrebidCache");function L(e,t,n){if(e&&n){if(e===A.G_){const e=(0,r.A)(t,`${A.G_}.context`,"instream");if(n[`${A.G_}-${e}`])return n[`${A.G_}-${e}`]}return n[e]}}const z=e=>t=>{const n=e||function(e){let{index:t=se.index}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};const n=L(e.mediaType,t.getMediaTypes(e),d.$W.getConfig("mediaTypePriceGranularity"));return"string"==typeof e.mediaType&&n?"string"==typeof n?n:"custom":d.$W.getConfig("priceGranularity")}(t);return n===C.UE.AUTO?t.pbAg:n===C.UE.DENSE?t.pbDg:n===C.UE.LOW?t.pbLg:n===C.UE.MEDIUM?t.pbMg:n===C.UE.HIGH?t.pbHg:n===C.UE.CUSTOM?t.pbCg:void 0},J=()=>e=>e.creativeId?e.creativeId:"",K=()=>e=>e.meta&&e.meta.advertiserDomains&&e.meta.advertiserDomains.length>0?[e.meta.advertiserDomains].flat()[0]:"",Q=()=>e=>e.meta&&(e.meta.networkId||e.meta.networkName)?(0,r.A)(e,"meta.networkName")||(0,r.A)(e,"meta.networkId"):"",Y=()=>e=>e.meta&&e.meta.primaryCatId?e.meta.primaryCatId:"";function X(e,t){return{key:e,val:"function"==typeof t?function(e,n){return t(e,n)}:function(e){return(0,i._W)(e,t)}}}function Z(e,t){const n=Object.assign({},w.u.settingsFor(null));if(n[C.iD.ADSERVER_TARGETING]||(n[C.iD.ADSERVER_TARGETING]=[X(C.xS.BIDDER,"bidderCode"),X(C.xS.AD_ID,"adId"),X(C.xS.PRICE_BUCKET,z()),X(C.xS.SIZE,"size"),X(C.xS.DEAL,"dealId"),X(C.xS.SOURCE,"source"),X(C.xS.FORMAT,"mediaType"),X(C.xS.ADOMAIN,K()),X(C.xS.ACAT,Y()),X(C.xS.DSP,Q()),X(C.xS.CRID,J())]),"video"===e){const e=n[C.iD.ADSERVER_TARGETING].slice();if(n[C.iD.ADSERVER_TARGETING]=e,[C.xS.UUID,C.xS.CACHE_ID].forEach((t=>{void 0===(0,v.I6)(e,(e=>e.key===t))&&e.push(X(t,"videoCacheKey"))})),d.$W.getConfig("cache.url")&&(!t||!1!==w.u.get(t,"sendStandardTargeting"))){const t=(0,i.Dl)(d.$W.getConfig("cache.url"));void 0===(0,v.I6)(e,(e=>e.key===C.xS.CACHE_HOST))&&e.push(X(C.xS.CACHE_HOST,(function(e){return(0,r.A)(e,`adserverTargeting.${C.xS.CACHE_HOST}`)?e.adserverTargeting[C.xS.CACHE_HOST]:t.hostname})))}}return n}function ee(e,t,n,r){var o=t[C.iD.ADSERVER_TARGETING];return n.size=n.getSize(),(o||[]).forEach((function(o){var s=o.key,a=o.val;if(e[s]&&(0,i.JE)("The key: "+s+" is being overwritten"),(0,i.fp)(a))try{a=a(n,r)}catch(e){(0,i.vV)("bidmanager","ERROR",e)}(void 0===t.suppressEmptyKeys||!0!==t.suppressEmptyKeys)&&s!==C.xS.DEAL&&s!==C.xS.ACAT&&s!==C.xS.DSP&&s!==C.xS.CRID||!(0,i.xQ)(a)&&null!=a?e[s]=a:(0,i.fH)("suppressing empty key '"+s+"' from adserver targeting")})),e}function te(e,t){return e[t.adUnitCode]||(e[t.adUnitCode]={bids:[]}),e[t.adUnitCode].bids.push(t),e}function ne(e){Object.assign(this,{getAuction(t){let{auctionId:n}=t;if(null!=n)return e().find((e=>e.getAuctionId()===n))},getAdUnit(t){let{adUnitId:n}=t;if(null!=n)return e().flatMap((e=>e.getAdUnits())).find((e=>e.adUnitId===n))},getMediaTypes(e){let{adUnitId:t,requestId:n}=e;if(null!=n){const e=this.getBidRequest({requestId:n});if(null!=e&&(null==t||e.adUnitId===t))return e.mediaTypes}else if(null!=t){const e=this.getAdUnit({adUnitId:t});if(null!=e)return e.mediaTypes}},getBidderRequest(t){let{requestId:n,bidderRequestId:i}=t;if(null!=n||null!=i){let t=e().flatMap((e=>e.getBidRequests()));return null!=i&&(t=t.filter((e=>e.bidderRequestId===i))),null==n?t[0]:t.find((e=>e.bids&&null!=e.bids.find((e=>e.bidId===n))))}},getBidRequest(t){let{requestId:n}=t;if(null!=n)return e().flatMap((e=>e.getBidRequests())).flatMap((e=>e.bids)).find((e=>e&&e.bidId===n))}})}var ie=n(6853),re=n(7863);const oe="minBidCacheTTL";const se=function(){let e=null;const t=(0,ie.H)({startTime:e=>e.end.then((()=>e.getAuctionEnd())),ttl:t=>null==e?null:t.end.then((()=>1e3*Math.max(e,...t.getBidsReceived().map(re.L))))});(0,re.u)((()=>{null!=e&&t.refresh()})),d.$W.getConfig(oe,(n=>{const i=e;e=n?.[oe],e="number"==typeof e?e:null,i!==e&&t.refresh()}));const n={onExpiry:t.onExpiry};function r(e){for(const n of t)if(n.getAuctionId()===e)return n}function o(){return t.toArray().flatMap((e=>e.getBidsReceived()))}return n.addWinningBid=function(e){const t=(0,B.BO)(e.metrics);t.checkpoint("bidWon"),t.timeBetween("auctionEnd","bidWon","adserver.pending"),t.timeBetween("requestBids","bidWon","adserver.e2e");const n=r(e.auctionId);n?n.addWinningBid(e):(0,i.JE)("Auction not found when adding winning bid")},Object.entries({getAllWinningBids:{name:"getWinningBids"},getBidsRequested:{name:"getBidRequests"},getNoBids:{},getAdUnits:{},getBidsReceived:{pre:e=>e.getAuctionStatus()===_},getAdUnitCodes:{post:i.hj}}).forEach((e=>{let[i,{name:r=i,pre:o,post:s}]=e;const a=null==o?e=>e[r]():e=>o(e)?e[r]():[],d=null==s?e=>e:e=>e.filter(s);n[i]=()=>d(t.toArray().flatMap(a))})),n.getAllBidsForAdUnitCode=function(e){return o().filter((t=>t&&t.adUnitCode===e))},n.createAuction=function(e){const n=W(e);return function(e){t.add(e)}(n),n},n.findBidByAdId=function(e){return o().find((t=>t.adId===e))},n.getStandardBidderAdServerTargeting=function(){return Z()[C.iD.ADSERVER_TARGETING]},n.setStatusForBids=function(e,t){let i=n.findBidByAdId(e);if(i&&(i.status=t),i&&t===C.tl.BID_TARGETING_SET){const e=r(i.auctionId);e&&e.setBidTargeting(i)}},n.getLastAuctionId=function(){const e=t.toArray();return e.length&&e[e.length-1].getAuctionId()},n.clearAllAuctions=function(){t.clear()},n.index=new ne((()=>t.toArray())),n}()},7863:(e,t,n)=>{n.d(t,{L:()=>a,u:()=>d});var i=n(3272),r=n(1069);let o=1;const s=[];function a(e){return e.ttl-(e.hasOwnProperty("ttlBuffer")?e.ttlBuffer:o)}function d(e){s.push(e)}i.$W.getConfig("ttlBuffer",(e=>{if("number"==typeof e.ttlBuffer){const t=o;o=e.ttlBuffer,t!==o&&s.forEach((e=>e(o)))}else(0,r.vV)("Invalid value for ttlBuffer",e.ttlBuffer)}))},2693:(e,t,n)=>{n.d(t,{u:()=>l});var i=n(433),r=n(1069),o=n(7873),s=n(8969);function a(e,t){(function(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")})(e,t),t.add(e)}function d(e,t,n){if("function"==typeof e?e===t:e.has(t))return arguments.length<3?t:n;throw new TypeError("Private element is not present on this object")}var c=new WeakSet;function u(e){return null==e?this.defaultScope:e}const l=new class{constructor(e,t){a(this,c),this.getSettings=e,this.defaultScope=t}get(e,t){let n=this.getOwn(e,t);return void 0===n&&(n=this.getOwn(null,t)),n}getOwn(e,t){return e=d(c,this,u).call(this,e),(0,i.A)(this.getSettings(),`${e}.${t}`)}getScopes(){return Object.keys(this.getSettings()).filter((e=>e!==this.defaultScope))}settingsFor(e){return(0,r.D9)({},this.ownSettingsFor(null),this.ownSettingsFor(e))}ownSettingsFor(e){return e=d(c,this,u).call(this,e),this.getSettings()[e]||{}}}((()=>(0,o.m)().bidderSettings||{}),s.iD.BD_SETTING_STANDARD)},3597:(e,t,n)=>{n.d(t,{O:()=>o});var i=n(1069);function r(e){let{src:t="client",bidder:n="",bidId:r,transactionId:o,adUnitId:s,auctionId:a}=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{};var d=t,c=e||0;Object.assign(this,{bidderCode:n,width:0,height:0,statusMessage:function(){switch(c){case 0:return"Pending";case 1:return"Bid available";case 2:return"Bid returned empty or error response";case 3:return"Bid timed out"}}(),adId:(0,i.s0)(),requestId:r,transactionId:o,adUnitId:s,auctionId:a,mediaType:"banner",source:d}),this.getStatusCode=function(){return c},this.getSize=function(){return this.width+"x"+this.height},this.getIdentifiers=function(){return{src:this.source,bidder:this.bidderCode,bidId:this.requestId,transactionId:this.transactionId,adUnitId:this.adUnitId,auctionId:this.auctionId}}}function o(e,t){return new r(e,t)}},3272:(e,t,n)=>{n.d(t,{$W:()=>h,Ov:()=>u});var i=n(6833),r=n(5901),o=n(1069),s=n(433),a=n(8969);const d="TRUE"===(0,o.Ez)(a.M).toUpperCase(),c={},u="random",l={};l[u]=!0,l.fixed=!0;const f=u,g={LOW:"low",MEDIUM:"medium",HIGH:"high",AUTO:"auto",DENSE:"dense",CUSTOM:"custom"};function p(e){const t=!(arguments.length>1&&void 0!==arguments[1])||arguments[1]?{priceGranularity:g.MEDIUM,customPriceBucket:{},mediaTypePriceGranularity:{},bidderSequence:f,auctionOptions:{}}:{};function n(e){return t[e]}function s(n,i){t.hasOwnProperty(n)||Object.defineProperty(e,n,{enumerable:!0}),t[n]=i}const a={publisherDomain:{set(e){null!=e&&(0,o.JE)("publisherDomain is deprecated and has no effect since v7 - use pageUrl instead"),s("publisherDomain",e)}},priceGranularity:{set(e){c(e)&&("string"==typeof e?s("priceGranularity",d(e)?e:g.MEDIUM):(0,o.Qd)(e)&&(s("customPriceBucket",e),s("priceGranularity",g.CUSTOM),(0,o.OG)("Using custom price granularity")))}},customPriceBucket:{},mediaTypePriceGranularity:{set(e){null!=e&&s("mediaTypePriceGranularity",Object.keys(e).reduce(((t,i)=>(c(e[i])?"string"==typeof e?t[i]=d(e[i])?e[i]:n("priceGranularity"):(0,o.Qd)(e)&&(t[i]=e[i],(0,o.OG)(`Using custom price granularity for ${i}`)):(0,o.JE)(`Invalid price granularity for media type: ${i}`),t)),{}))}},bidderSequence:{set(e){l[e]?s("bidderSequence",e):(0,o.JE)(`Invalid order: ${e}. Bidder Sequence was not set.`)}},auctionOptions:{set(e){(function(e){if(!(0,o.Qd)(e))return(0,o.JE)("Auction Options must be an object"),!1;for(let t of Object.keys(e)){if("secondaryBidders"!==t&&"suppressStaleRender"!==t)return(0,o.JE)(`Auction Options given an incorrect param: ${t}`),!1;if("secondaryBidders"===t){if(!(0,o.cy)(e[t]))return(0,o.JE)(`Auction Options ${t} must be of type Array`),!1;if(!e[t].every(o.O8))return(0,o.JE)(`Auction Options ${t} must be only string`),!1}else if("suppressStaleRender"===t&&!(0,o.Lm)(e[t]))return(0,o.JE)(`Auction Options ${t} must be of type boolean`),!1}return!0})(e)&&s("auctionOptions",e)}}};return Object.defineProperties(e,Object.fromEntries(Object.entries(a).map((e=>{let[i,r]=e;return[i,Object.assign({get:n.bind(null,i),set:s.bind(null,i),enumerable:t.hasOwnProperty(i),configurable:!t.hasOwnProperty(i)},r)]})))),e;function d(e){return(0,r.I6)(Object.keys(g),(t=>e===g[t]))}function c(e){if(!e)return(0,o.vV)("Prebid Error: no value passed to `setPriceGranularity()`"),!1;if("string"==typeof e)d(e)||(0,o.JE)("Prebid Warning: setPriceGranularity was called with invalid setting, using `medium` as default.");else if((0,o.Qd)(e)&&!(0,i.q)(e))return(0,o.vV)("Invalid custom price value passed to `setPriceGranularity()`"),!1;return!0}}const h=function(){let e,t,n,i=[],a=null;function u(){e={};let i=p({debug:d,bidderTimeout:3e3,enableSendAllBids:true,useBidCache:false,deviceAccess:true,disableAjaxTimeout:false,maxNestedIframes:10,maxBid:5e3,userSync:{topics:c}});t&&v(Object.keys(t).reduce(((e,n)=>(t[n]!==i[n]&&(e[n]=i[n]||{}),e)),{})),t=i,n={}}function l(){if(a&&n&&(0,o.Qd)(n[a])){let e=n[a];const i=new Set(Object.keys(t).concat(Object.keys(e)));return(0,r.A6)(i).reduce(((n,i)=>(void 0===e[i]?n[i]=t[i]:void 0===t[i]?n[i]=e[i]:(0,o.Qd)(e[i])?n[i]=(0,o.D9)({},t[i],e[i]):n[i]=e[i],n)),{})}return Object.assign({},t)}const[f,g]=[l,function(){const e=l();return Object.defineProperty(e,"ortb2",{get:function(){throw new Error("invalid access to 'orbt2' config - use request parameters instead")}}),e}].map((e=>function(){if(arguments.length<=1&&"function"!=typeof(arguments.length<=0?void 0:arguments[0])){const t=arguments.length<=0?void 0:arguments[0];return t?(0,s.A)(e(),t):l()}return y(...arguments)})),[h,m]=[g,f].map((e=>function(){let t=e(...arguments);return t&&"object"==typeof t&&(t=(0,o.Go)(t)),t}));function b(n){if(!(0,o.Qd)(n))return void(0,o.vV)("setConfig options must be an object");let i=Object.keys(n),r={};i.forEach((i=>{let s=n[i];(0,o.Qd)(e[i])&&(0,o.Qd)(s)&&(s=Object.assign({},e[i],s));try{r[i]=t[i]=s}catch(e){(0,o.JE)(`Cannot set config for property ${i} : `,e)}})),v(r)}function y(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:{},r=t;if("string"!=typeof e&&(r=e,e="*",n=t||{}),"function"!=typeof r)return void(0,o.vV)("listener must be a function");const s={topic:e,callback:r};return i.push(s),n.init&&r("*"===e?g():{[e]:g(e)}),function(){i.splice(i.indexOf(s),1)}}function v(e){const t=Object.keys(e);i.filter((e=>(0,r.mK)(t,e.topic))).forEach((t=>{t.callback({[t.topic]:e[t.topic]})})),i.filter((e=>"*"===e.topic)).forEach((t=>t.callback(e)))}function E(e){let t=arguments.length>1&&void 0!==arguments[1]&&arguments[1];try{!function(e){if(!(0,o.Qd)(e))throw"setBidderConfig bidder options must be an object";if(!Array.isArray(e.bidders)||!e.bidders.length)throw"setBidderConfig bidder options must contain a bidders list with at least 1 bidder";if(!(0,o.Qd)(e.config))throw"setBidderConfig bidder options must contain a config object"}(e),e.bidders.forEach((i=>{n[i]||(n[i]=p({},!1)),Object.keys(e.config).forEach((r=>{let s=e.config[r];const a=n[i][r];if((0,o.Qd)(s)&&(null==a||(0,o.Qd)(a))){const e=t?o.D9:Object.assign;n[i][r]=e({},a||{},s)}else n[i][r]=s}))}))}catch(e){(0,o.vV)(e)}}function A(e,t){a=e;try{return t()}finally{w()}}function w(){a=null}return u(),{getCurrentBidder:function(){return a},resetBidder:w,getConfig:g,getAnyConfig:f,readConfig:h,readAnyConfig:m,setConfig:b,mergeConfig:function(e){if(!(0,o.Qd)(e))return void(0,o.vV)("mergeConfig input must be an object");const t=(0,o.D9)(l(),e);return b({...t}),t},setDefaults:function(n){(0,o.Qd)(e)?(Object.assign(e,n),Object.assign(t,n)):(0,o.vV)("defaults must be an object")},resetConfig:u,runWithBidder:A,callbackWithBidder:function(e){return function(t){return function(){if("function"==typeof t){for(var n=arguments.length,i=new Array(n),r=0;r{n.d(t,{B1:()=>f,SL:()=>k,ad:()=>I,et:()=>C,mW:()=>w,o2:()=>O,t6:()=>T});var i=n(4705),r=n(1069),o=n(5555),s=n(3272);function a(e,t,n){d(e,t),t.set(e,n)}function d(e,t){if(t.has(e))throw new TypeError("Cannot initialize the same private elements twice on an object")}function c(e,t){return e.get(l(e,t))}function u(e,t,n){return e.set(l(e,t),n),n}function l(e,t,n){if("function"==typeof e?e===t:e.has(t))return arguments.length<3?t:n;throw new TypeError("Private element is not present on this object")}const f=Object.freeze({});var g=new WeakMap,p=new WeakMap,h=new WeakMap,m=new WeakMap,b=new WeakMap,y=new WeakMap,v=new WeakSet;class E{constructor(){var e,t;d(e=this,t=v),t.add(e),a(this,g,void 0),a(this,p,void 0),a(this,h,void 0),a(this,m,void 0),a(this,b,!0),a(this,y,void 0),(0,i.A)(this,"generatedTime",void 0),(0,i.A)(this,"hashFields",void 0),this.reset()}reset(){u(h,this,(0,o.v)()),u(g,this,!1),u(p,this,null),u(m,this,!1),this.generatedTime=null}enable(){u(g,this,!0)}get enabled(){return c(g,this)}get ready(){return c(m,this)}get promise(){return c(m,this)?o.k.resolve(c(p,this)):(c(g,this)||l(v,this,A).call(this,null),c(h,this).promise)}setConsentData(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:(0,r.vE)();this.generatedTime=t,u(b,this,!0),l(v,this,A).call(this,e)}getConsentData(){return c(p,this)}get hash(){return c(b,this)&&(u(y,this,(0,r.PB)(JSON.stringify(c(p,this)&&this.hashFields?this.hashFields.map((e=>c(p,this)[e])):c(p,this)))),u(b,this,!1)),c(y,this)}}function A(e){u(m,this,!0),u(p,this,e),c(h,this).resolve(e)}const w=new class extends E{hashFields=["gdprApplies","consentString"];getConsentMeta(){const e=this.getConsentData();if(e&&e.vendorData&&this.generatedTime)return{gdprApplies:e.gdprApplies,consentStringSize:(0,r.O8)(e.vendorData.tcString)?e.vendorData.tcString.length:0,generatedAt:this.generatedTime,apiVersion:e.apiVersion}}},T=new class extends E{getConsentMeta(){if(this.getConsentData()&&this.generatedTime)return{generatedAt:this.generatedTime}}},I=new class extends E{hashFields=["applicableSections","gppString"];getConsentMeta(){if(this.getConsentData()&&this.generatedTime)return{generatedAt:this.generatedTime}}},C=(()=>{function e(){return!!s.$W.getConfig("coppa")}return{getCoppa:e,getConsentData:e,getConsentMeta:e,reset(){},get promise(){return o.k.resolve(e())},get hash(){return e()?"1":"0"}}})(),O=function(){const e={},t={},n={};return{register(i,r,o){o&&((e[r]=e[r]||{})[i]=o,t.hasOwnProperty(r)?t[r]!==o&&(t[r]=n):t[r]=o)},get(i){const r={modules:e[i]||{}};return t.hasOwnProperty(i)&&t[i]!==n&&(r.gvlid=t[i]),r}}}(),B={gdpr:w,usp:T,gpp:I,coppa:C};const k=function(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:B;return e=Object.entries(e),Object.assign({get promise(){return o.k.all(e.map((e=>{let[t,n]=e;return n.promise.then((e=>[t,e]))}))).then((e=>Object.fromEntries(e)))},get hash(){return(0,r.PB)(e.map((e=>{let[t,n]=e;return n.hash})).join(":"))}},Object.fromEntries(["getConsentData","getConsentMeta","reset"].map((t=>{return[t,(n=t,function(){return Object.fromEntries(e.map((e=>{let[t,i]=e;return[t,i[n]()]})))})];var n}))))}()},8969:(e,t,n)=>{n.d(t,{IY:()=>A,M:()=>r,RW:()=>g,Tf:()=>h,UE:()=>c,XQ:()=>o,Zh:()=>l,_B:()=>v,as:()=>a,cA:()=>d,h0:()=>m,iD:()=>i,jO:()=>b,nl:()=>E,oA:()=>y,qY:()=>s,tl:()=>p,x5:()=>f,xS:()=>u});const i={PL_CODE:"code",PL_SIZE:"sizes",PL_BIDS:"bids",BD_BIDDER:"bidder",BD_ID:"paramsd",BD_PL_ID:"placementId",ADSERVER_TARGETING:"adserverTargeting",BD_SETTING_STANDARD:"standard"},r="pbjs_debug",o={GOOD:1},s={AUCTION_INIT:"auctionInit",AUCTION_TIMEOUT:"auctionTimeout",AUCTION_END:"auctionEnd",BID_ADJUSTMENT:"bidAdjustment",BID_TIMEOUT:"bidTimeout",BID_REQUESTED:"bidRequested",BID_RESPONSE:"bidResponse",BID_REJECTED:"bidRejected",NO_BID:"noBid",SEAT_NON_BID:"seatNonBid",BID_WON:"bidWon",BIDDER_DONE:"bidderDone",BIDDER_ERROR:"bidderError",SET_TARGETING:"setTargeting",BEFORE_REQUEST_BIDS:"beforeRequestBids",BEFORE_BIDDER_HTTP:"beforeBidderHttp",REQUEST_BIDS:"requestBids",ADD_AD_UNITS:"addAdUnits",AD_RENDER_FAILED:"adRenderFailed",AD_RENDER_SUCCEEDED:"adRenderSucceeded",TCF2_ENFORCEMENT:"tcf2Enforcement",AUCTION_DEBUG:"auctionDebug",BID_VIEWABLE:"bidViewable",STALE_RENDER:"staleRender",BILLABLE_EVENT:"billableEvent",BID_ACCEPTED:"bidAccepted",RUN_PAAPI_AUCTION:"paapiRunAuction",PBS_ANALYTICS:"pbsAnalytics",PAAPI_BID:"paapiBid",PAAPI_NO_BID:"paapiNoBid",PAAPI_ERROR:"paapiError"},a={PREVENT_WRITING_ON_MAIN_DOCUMENT:"preventWritingOnMainDocument",NO_AD:"noAd",EXCEPTION:"exception",CANNOT_FIND_AD:"cannotFindAd",MISSING_DOC_OR_ADID:"missingDocOrAdid"},d={bidWon:"adUnitCode"},c={LOW:"low",MEDIUM:"medium",HIGH:"high",AUTO:"auto",DENSE:"dense",CUSTOM:"custom"},u={BIDDER:"hb_bidder",AD_ID:"hb_adid",PRICE_BUCKET:"hb_pb",SIZE:"hb_size",DEAL:"hb_deal",SOURCE:"hb_source",FORMAT:"hb_format",UUID:"hb_uuid",CACHE_ID:"hb_cache_id",CACHE_HOST:"hb_cache_host",ADOMAIN:"hb_adomain",ACAT:"hb_acat",CRID:"hb_crid",DSP:"hb_dsp"},l={BIDDER:"hb_bidder",AD_ID:"hb_adid",PRICE_BUCKET:"hb_pb",SIZE:"hb_size",DEAL:"hb_deal",FORMAT:"hb_format",UUID:"hb_uuid",CACHE_HOST:"hb_cache_host"},f={title:"hb_native_title",body:"hb_native_body",body2:"hb_native_body2",privacyLink:"hb_native_privacy",privacyIcon:"hb_native_privicon",sponsoredBy:"hb_native_brand",image:"hb_native_image",icon:"hb_native_icon",clickUrl:"hb_native_linkurl",displayUrl:"hb_native_displayurl",cta:"hb_native_cta",rating:"hb_native_rating",address:"hb_native_address",downloads:"hb_native_downloads",likes:"hb_native_likes",phone:"hb_native_phone",price:"hb_native_price",salePrice:"hb_native_saleprice",rendererUrl:"hb_renderer_url",adTemplate:"hb_adTemplate"},g={SRC:"s2s",DEFAULT_ENDPOINT:"https://prebid.adnxs.com/pbs/v1/openrtb2/auction",SYNCED_BIDDERS_KEY:"pbjsSyncs"},p={BID_TARGETING_SET:"targetingSet",RENDERED:"rendered",BID_REJECTED:"bidRejected"},h={INVALID:"Bid has missing or invalid properties",INVALID_REQUEST_ID:"Invalid request ID",BIDDER_DISALLOWED:"Bidder code is not allowed by allowedAlternateBidderCodes / allowUnknownBidderCodes",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",PRICE_TOO_HIGH:"Bid price exceeds maximum value"},m={body:"desc",body2:"desc2",sponsoredBy:"sponsored",cta:"ctatext",rating:"rating",address:"address",downloads:"downloads",likes:"likes",phone:"phone",price:"price",salePrice:"saleprice",displayUrl:"displayurl"},b={sponsored:1,desc:2,rating:3,likes:4,downloads:5,price:6,saleprice:7,phone:8,address:9,desc2:10,displayurl:11,ctatext:12},y={ICON:1,MAIN:3},v=["privacyIcon","clickUrl","sendTargetingKeys","adTemplate","rendererUrl","type"],E={REQUEST:"Prebid Request",RESPONSE:"Prebid Response",NATIVE:"Prebid Native",EVENT:"Prebid Event"},A="__pb_locator__"},6833:(e,t,n)=>{n.d(t,{j:()=>f,q:()=>p});var i=n(5901),r=n(1069),o=n(3272);const s=2,a={buckets:[{max:5,increment:.5}]},d={buckets:[{max:20,increment:.1}]},c={buckets:[{max:20,increment:.01}]},u={buckets:[{max:3,increment:.01},{max:8,increment:.05},{max:20,increment:.5}]},l={buckets:[{max:5,increment:.05},{max:10,increment:.1},{max:20,increment:.5}]};function f(e,t){let n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:1,i=parseFloat(e);return isNaN(i)&&(i=""),{low:""===i?"":g(e,a,n),med:""===i?"":g(e,d,n),high:""===i?"":g(e,c,n),auto:""===i?"":g(e,l,n),dense:""===i?"":g(e,u,n),custom:""===i?"":g(e,t,n)}}function g(e,t,n){let a="";if(!p(t))return a;const d=t.buckets.reduce(((e,t)=>e.max>t.max?e:t),{max:0});let c=0,u=(0,i.I6)(t.buckets,(t=>{if(e>d.max*n){let e=t.precision;void 0===e&&(e=s),a=(t.max*n).toFixed(e)}else{if(e<=t.max*n&&e>=c*n)return t.min=c,t;c=t.max}}));return u&&(a=function(e,t,n){const i=void 0!==t.precision?t.precision:s,a=t.increment*n,d=t.min*n;let c=Math.floor,u=o.$W.getConfig("cpmRoundingFunction");"function"==typeof u&&(c=u);let l,f,g=Math.pow(10,i+2),p=(e*g-d*g)/(a*g);try{l=c(p)*a+d}catch(e){f=!0}(f||"number"!=typeof l)&&((0,r.JE)("Invalid rounding function passed in config"),l=Math.floor(p)*a+d);return l=Number(l.toFixed(10)),l.toFixed(i)}(e,u,n)),a}function p(e){if((0,r.Im)(e)||!e.buckets||!Array.isArray(e.buckets))return!1;let t=!0;return e.buckets.forEach((e=>{e.max&&e.increment||(t=!1)})),t}},6031:(e,t,n)=>{n.d(t,{H:()=>a,k:()=>s});var i=n(5555),r=n(1069),o=n(4595);const s=(0,n(9214).A_)("sync",(function(e){return o.G})),a=function(){const e={};return function(t){const n=s(t);return e.hasOwnProperty(n)||(e[n]=new i.k((e=>{const t=(0,r.CA)();t.srcdoc=` + + + + + + + + + + + + +

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..eecfea4b70e --- /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('bidderOperations', 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..650ff668070 --- /dev/null +++ b/test/spec/libraries/teqblazeUtils/bidderUtils_spec.js @@ -0,0 +1,519 @@ +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', + '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://${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/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..9b634970ebb --- /dev/null +++ b/test/spec/modules/51DegreesRtdProvider_spec.js @@ -0,0 +1,276 @@ +import { + extractConfig, + get51DegreesJSURL, + is51DegreesMetaPresent, + setOrtb2KeyIfNotEmpty, + convert51DegreesDeviceToOrtb2, + getBidRequestData, + fiftyOneDegreesSubmodule, +} from 'modules/51DegreesRtdProvider'; + +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() { + 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(); + }); + }); + + describe('get51DegreesJSURL', function() { + it('returns the cloud URL if the resourceKey is provided', function() { + const config = {resourceKey: 'TEST_RESOURCE_KEY'}; + expect(get51DegreesJSURL(config)).to.equal( + 'https://cloud.51degrees.com/api/v4/TEST_RESOURCE_KEY.js' + ); + }); + + it('returns the on-premise URL if the onPremiseJSUrl is provided', function() { + const config = {onPremiseJSUrl: 'https://example.com/51Degrees.core.js'}; + expect(get51DegreesJSURL(config)).to.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('setOrtb2KeyIfNotEmpty', function() { + it('sets value of ORTB2 key if it is not empty', function() { + const data = {}; + setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', 'TEST_ORTB2_VALUE'); + expect(data).to.deep.equal({TEST_ORTB2_KEY: 'TEST_ORTB2_VALUE'}); + }); + + it('throws an error if the key is empty', function() { + const data = {}; + expect(() => setOrtb2KeyIfNotEmpty(data, '', 'TEST_ORTB2_VALUE')).to.throw(); + }); + + it('does not set value of ORTB2 key if it is empty', function() { + const data = {}; + setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', ''); + setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', 0); + setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', null); + setOrtb2KeyIfNotEmpty(data, 'TEST_ORTB2_KEY', undefined); + expect(data).to.deep.equal({}); + }); + }); + + describe('convert51DegreesDeviceToOrtb2', 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', + }; + + it('converts 51Degrees device data to ORTB2 format', function() { + expect(convert51DegreesDeviceToOrtb2(fiftyOneDegreesDevice)).to.deep.equal({ + 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', + }, + }); + }); + + 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)).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)).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)).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)).to.not.have.any.keys('ppi'); + }); + }); + + describe('getBidRequestData', function() { + let initialHeadInnerHTML; + const reqBidsConfigObj = { + ortb2Fragments: { + global: { + device: {}, + }, + }, + }; + + before(function() { + initialHeadInnerHTML = document.head.innerHTML; + + const mockScript = document.createElement('script'); + mockScript.innerHTML = ` + 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', + }; + window.fod = {complete: (_callback) => _callback({device: fiftyOneDegreesDevice})}; + `; + document.head.appendChild(mockScript); + }); + + 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; + }); + }); + + 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/acuityadsBidAdapter_spec.js b/test/spec/modules/acuityadsBidAdapter_spec.js index 31ef9dd6466..526bbf6fd54 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 () { @@ -130,7 +153,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 +169,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 +195,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 +213,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 +448,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..fd899a1c864 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,7 +277,14 @@ 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': [ { @@ -382,11 +345,41 @@ const AUCTION_INIT_ANOTHER = { 'auctionId': AUCTION_ID, 'src': 'client', 'bidRequestsCount': 1 - } + }, { + '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'] + }, + 'ortb2': { + 'site': { + 'ext': { + 'data': { + 'adg_rtd': { + ...ADG_RTD + }, + ...ORTB_DATA + } + } + } } }, { 'bidderCode': 'adagio', @@ -395,8 +388,7 @@ const AUCTION_INIT_ANOTHER = { 'bids': [ { 'bidder': 'adagio', 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_ADAGIO + ...PARAMS_ADG }, 'mediaTypes': { 'banner': { @@ -416,6 +408,18 @@ const AUCTION_INIT_ANOTHER = { '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 + }, + ...ORTB_DATA + } + } + } } } ], @@ -456,7 +460,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 +487,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 +542,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 +563,7 @@ const AUCTION_INIT_CACHE = { 'bids': [ { 'bidder': 'adagio', 'params': { - ...PARAMS_ADG, - adagioAuctionId: AUCTION_ID_CACHE_ADAGIO + ...PARAMS_ADG }, 'mediaTypes': { 'banner': { @@ -553,6 +583,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 +612,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, @@ -621,10 +680,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 +708,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 +737,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 +745,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 +756,8 @@ 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.adg_mts).to.equal('ban'); } @@ -695,20 +766,39 @@ 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,,'); } { - 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'); + } + + { + 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'); @@ -719,20 +809,6 @@ describe('adagio analytics adapter', () => { }); 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 +817,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 +838,7 @@ 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.adg_mts).to.equal('ban'); expect(search.t_n).to.equal('test'); expect(search.t_v).to.equal('version'); @@ -772,7 +851,29 @@ 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.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 +883,55 @@ 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.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'); + } + + { + 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(',,,'); } { - 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; + } + + { + 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'); @@ -816,20 +942,28 @@ describe('adagio analytics adapter', () => { } { - 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'); } }); 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 +972,74 @@ 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; } }); }); 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..6dbe4b34985 --- /dev/null +++ b/test/spec/modules/adagioRtdProvider_spec.js @@ -0,0 +1,677 @@ +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 + }; + + 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() + } + } + + 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' + }; + + 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 + } + } + } + } + } + }, + }, + ], + '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 + } + } + } + } + } + }, + '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..f7da18403fd --- /dev/null +++ b/test/spec/modules/adgridBidAdapter_spec.js @@ -0,0 +1,100 @@ +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 + } + }]; + + 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 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('Test the API Method', function () { + expect(method).to.equal(globalConfig.method); + }); + }); + + 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/admanBidAdapter_spec.js b/test/spec/modules/admanBidAdapter_spec.js index a9413860072..cca5d57db0f 100644 --- a/test/spec/modules/admanBidAdapter_spec.js +++ b/test/spec/modules/admanBidAdapter_spec.js @@ -1,343 +1,466 @@ -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', + '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..12cb3eaa2bb 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 @@ -474,7 +475,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 +488,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 +511,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', @@ -566,7 +572,7 @@ describe('adnuntiusBidAdapter', function () { expect(request[0].url).to.equal(ENDPOINT_URL_VIDEO); }); - it('should pass segments if available in config', function () { + it('should pass segments if available in config and merge from targeting', function () { const ortb2 = { user: { data: [{ @@ -580,46 +586,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 +922,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'); }); }); @@ -925,7 +1028,9 @@ describe('adnuntiusBidAdapter', function () { config.setBidderConfig({ bidders: ['adnuntius'], config: { - maxDeals: 2 + maxDeals: 2, + useCookie: 'ignore-this', + advertiserTransparency: 'ignore-this-as-well' } }); @@ -1012,30 +1117,30 @@ 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 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/adprimeBidAdapter_spec.js b/test/spec/modules/adprimeBidAdapter_spec.js index 5efed4ec5ab..71aeccb2975 100644 --- a/test/spec/modules/adprimeBidAdapter_spec.js +++ b/test/spec/modules/adprimeBidAdapter_spec.js @@ -1,131 +1,256 @@ -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', + '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 +266,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let bannerResponses = spec.interpretResponse(banner); @@ -149,15 +277,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 +300,10 @@ describe('AdprimebBidAdapter', function () { netRevenue: true, currency: 'USD', dealId: '1', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let videoResponses = spec.interpretResponse(video); @@ -206,7 +337,10 @@ describe('AdprimebBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', - meta: {} + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -293,6 +427,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 +452,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/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/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..c9d29ff09dd 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 = { @@ -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/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..0b261c80848 100644 --- a/test/spec/modules/aniviewBidAdapter_spec.js +++ b/test/spec/modules/aniviewBidAdapter_spec.js @@ -31,12 +31,12 @@ describe('ANIVIEW Bid Adapter Test', 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 = { something: 'is wrong' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); }); 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..05c0982c80e 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,230 @@ 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'); 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, + '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({ + '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 +2408,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..c1e6adef0c6 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 () { @@ -139,7 +162,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 +179,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 +207,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 +225,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 +462,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/beyondmediaBidAdapter_spec.js b/test/spec/modules/beyondmediaBidAdapter_spec.js index 751b3ae1098..6bc071eb780 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; @@ -131,7 +153,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 +194,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 +212,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 +447,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/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..dcf35d032ea --- /dev/null +++ b/test/spec/modules/bidmaticBidAdapter_spec.js @@ -0,0 +1,268 @@ +import { expect } from 'chai'; +import { END_POINT, spec } from 'modules/bidmaticBidAdapter.js'; +import { deepClone, deepSetValue, mergeDeep } from '../../../src/utils'; + +const expectedImp = { + '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/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..2a2cd070d4c 100644 --- a/test/spec/modules/boldwinBidAdapter_spec.js +++ b/test/spec/modules/boldwinBidAdapter_spec.js @@ -1,115 +1,220 @@ -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', + '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 +224,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 +234,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 +263,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 +275,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 +297,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 +335,10 @@ describe('BoldwinBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -307,14 +426,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/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..4f0f2cf8f20 100644 --- a/test/spec/modules/cadentApertureMXBidAdapter_spec.js +++ b/test/spec/modules/cadentApertureMXBidAdapter_spec.js @@ -451,10 +451,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/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..98c79fb22e2 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 }; @@ -129,7 +153,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 +169,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 +244,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 +262,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 +497,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..5c01e23b027 100644 --- a/test/spec/modules/connatixBidAdapter_spec.js +++ b/test/spec/modules/connatixBidAdapter_spec.js @@ -1,9 +1,20 @@ 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, + 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 +35,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 +515,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 +531,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 +547,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; @@ -197,6 +685,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 +815,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(); 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..7c7d3d4ef2a 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 }; @@ -132,7 +153,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 +163,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 +244,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 +262,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 +472,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 +497,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/contxtfulRtdProvider_spec.js b/test/spec/modules/contxtfulRtdProvider_spec.js index 541c0e6e6dd..ad79b051393 100644 --- a/test/spec/modules/contxtfulRtdProvider_spec.js +++ b/test/spec/modules/contxtfulRtdProvider_spec.js @@ -1,25 +1,40 @@ -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 * as events from '../../../src/events'; -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 RX_API_MOCK = { receptivity: sinon.stub(), }; +const RX_CONNECTOR_MOCK = { + fetchConfig: sinon.stub(), + rxApiBuilder: sinon.stub(), +}; -const CONTXTFUL_API = { GetReceptivity: sinon.stub() } -const RX_ENGINE_IS_READY_EVENT = new CustomEvent('rxEngineIsReady', {detail: CONTXTFUL_API}); +const TIMEOUT = 10; +const RX_CONNECTOR_IS_READY_EVENT = new CustomEvent('rxConnectorIsReady', { detail: {[CUSTOMER]: RX_CONNECTOR_MOCK}, bubbles: true }); + +function writeToStorage(requester, timeDiff) { + let rx = RX_FROM_SESSION_STORAGE; + let exp = new Date().getTime() + timeDiff; + let item = { rx, exp, }; + sessionStorage.setItem(requester, JSON.stringify(item),); +} function buildInitConfig(version, customer) { return { name: 'contxtful', params: { - version, - customer, + version: version, + customer: customer, + hostname: 'api.receptivity.io', + bidders: ['mock-bidder-code'], + adServerTargeting: true, }, }; } @@ -33,9 +48,19 @@ describe('contxtfulRtdProvider', function () { loadExternalScriptTag = document.createElement('script'); loadExternalScriptStub.callsFake((_url, _moduleName) => loadExternalScriptTag); - CONTXTFUL_API.GetReceptivity.reset(); + RX_API_MOCK.receptivity.reset(); + RX_API_MOCK.receptivity.callsFake((tagId) => RX_FROM_API); + + 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 +68,7 @@ describe('contxtfulRtdProvider', function () { sandbox.restore(); }); - describe('extractParameters with invalid configuration', () => { + describe('extractParameters', () => { const { params: { customer, version }, } = buildInitConfig(VERSION, CUSTOMER); @@ -87,27 +112,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 +140,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); - contxtfulSubmodule.getTargetingData(['ad-slot']); + 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); + }); + }); + + 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 +228,340 @@ 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': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, - 'ad-slot-2': { ReceptivityState: 'INITIAL_RECEPTIVITY' }, + '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); + 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': 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); + // TODO: commented out because of rule violations + /* + it('uses non-expired info from session storage and adds receptivity to the ad units using session storage', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + // Simulate that there was a write to sessionStorage in the past. + writeToStorage(config.params.customer, +100); + contxtfulSubmodule.init(config); - expect(contxtfulSubmodule.getTargetingData(adUnits)).to.deep.equal( + setTimeout(() => { + expect(contxtfulSubmodule.getTargetingData(adUnits, config)).to.deep.equal( + expected + ); + 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('ignores expired info from session storage and does not forward the info to ad units', function (done) { + let config = buildInitConfig(VERSION, CUSTOMER); + // Simulate that there was a write to sessionStorage in the past. + writeToStorage(config.params.customer, -100); + contxtfulSubmodule.init(config); + expect(contxtfulSubmodule.getTargetingData(adUnits, config)).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 expectedOrtb2 = { + user: { + data: [ + { + name: 'contxtful', + ext: { + rx: RX_FROM_API, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + setTimeout(() => { + const onDone = () => undefined; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, onDone, config); + let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]; + expect(actualOrtb2).to.deep.equal(expectedOrtb2); + done(); + }, TIMEOUT); + }); + }); + + describe('getBidRequestData', function () { + // TODO: commented out because of rule violations + /* + 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. + writeToStorage(config.params.bidders[0], +100); + + contxtfulSubmodule.init(config); + + let reqBidsConfigObj = { + ortb2Fragments: { + global: {}, + bidder: {}, + }, + }; + + let expectedOrtb2 = { + user: { + data: [ + { + name: 'contxtful', + ext: { + rx: RX_FROM_SESSION_STORAGE, + params: { + ev: config.params?.version, + ci: config.params?.customer, + }, + }, + }, + ], + }, + }; + + // Since the RX_CONNECTOR_IS_READY_EVENT event was not dispatched, the RX engine is not loaded. + setTimeout(() => { + const noOp = () => undefined; + contxtfulSubmodule.getBidRequestData(reqBidsConfigObj, noOp, buildInitConfig(VERSION, CUSTOMER)); + let actualOrtb2 = reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]; + expect(actualOrtb2).to.deep.equal(expectedOrtb2); + 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.receptivity.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 ortb2 = { + user: { + data: [ + { + 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); + expect(reqBidsConfigObj.ortb2Fragments.bidder[config.params.bidders[0]]).to.deep.equal(ortb2); + 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..cc4cb832450 --- /dev/null +++ b/test/spec/modules/copper6sspBidAdapter_spec.js @@ -0,0 +1,514 @@ +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', + '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..123117027ea 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,8 @@ describe('The Criteo bidding adapter', function () { getCookieStub, setCookieStub, getDataFromLocalStorageStub, - removeDataFromLocalStorageStub; + removeDataFromLocalStorageStub, + triggerPixelStub; beforeEach(function () { getConfigStub = sinon.stub(config, 'getConfig'); @@ -143,6 +147,8 @@ describe('The Criteo bidding adapter', function () { setCookieStub = sinon.stub(storage, 'setCookie'); getDataFromLocalStorageStub = sinon.stub(storage, 'getDataFromLocalStorage'); removeDataFromLocalStorageStub = sinon.stub(storage, 'removeDataFromLocalStorage'); + + triggerPixelStub = sinon.stub(utils, 'triggerPixel'); }); afterEach(function () { @@ -155,14 +161,7 @@ describe('The Criteo bidding adapter', function () { setCookieStub.restore(); getDataFromLocalStorageStub.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 +305,30 @@ 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); + }); }); describe('isBidRequestValid', function () { @@ -602,8 +625,8 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 1, - consentString: 'concentDataString', + gdprApplies: true, + consentString: 'consentDataString', vendorData: { vendorConsents: { '91': 1 @@ -615,6 +638,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 +655,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 +668,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 +686,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -670,7 +694,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 +713,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 +733,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 +756,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 +781,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 +820,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 +904,7 @@ describe('The Criteo bidding adapter', function () { }, timeout: 3000, gdprConsent: { - gdprApplies: 0, + gdprApplies: false, consentString: undefined, vendorData: { vendorConsents: { @@ -982,23 +932,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 +992,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 +1019,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1082,9 +1034,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 +1044,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1108,42 +1059,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 +1116,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 +1126,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1192,10 +1143,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 +1153,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1234,10 +1183,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 +1196,6 @@ describe('The Criteo bidding adapter', function () { bidder: 'criteo', schain: expectedSchain, adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1261,8 +1207,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 +1217,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1288,9 +1233,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 +1243,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1316,9 +1259,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 +1269,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1344,313 +1285,245 @@ 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 () { + 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: { + pageType: 'article' + } + } + }; + const userData = { + gender: 'M', + data: [{ + name: 'some_provider', + ext: { + segtax: 3 + }, + segment: [ + { 'id': '1001' }, + { 'id': '1002' } + ] + }], + ext: { + data: { + registered: true + } + } + }; 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' } - ] - }] - }, - ext: { - data: { - pageType: 'article' - } - } - }; - const userData = { - gender: 'M', - data: [{ - name: 'some_provider', - ext: { - segtax: 3 - }, - segment: [ - { 'id': '1001' }, - { 'id': '1002' } - ] - }], - ext: { - data: { - registered: true - } - } - }; - const bidRequests = [ - { - bidder: 'criteo', - adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[728, 90]] @@ -1677,41 +1550,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 +1585,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1747,8 +1612,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 +1626,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { banner: { sizes: [[300, 250], [728, 90]] @@ -1775,8 +1639,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 +1653,6 @@ describe('The Criteo bidding adapter', function () { { bidder: 'criteo', adUnitCode: 'bid-123', - transactionId: 'transaction-123', mediaTypes: { video: { playerSize: [[300, 250], [728, 90]] @@ -1817,8 +1680,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 +1689,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 +1769,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 +1793,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 +1923,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 +1941,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' - } - } - }], - }, - }; - const request = { - bidRequests: [{ - adUnitCode: 'test-requestId', - bidId: 'test-bidId', - mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - params: { - networkId: 456, + 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 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 +2188,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 +2317,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: { @@ -2377,299 +2350,145 @@ describe('The Criteo bidding adapter', function () { adUnitCode: 'test-requestId', bidId: 'test-bidId2', mediaTypes: { - banner: { - sizes: [[728, 90]] - } - }, - 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); - }); - - 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, - }, - }] - }; - 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, - }, - }] - }; - 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 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 } - }, - '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' }] - } - }], - }, - }; - 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 () { } - }, - }, - { - bidder: 'criteo', - adUnitCode: 'bid-456', - transactionId: 'transaction-456', - sizes: [[728, 90]], - params: { - zoneId: 456, - publisherSubId: '456', - nativeCallback: function () { } + banner: { + sizes: [[728, 90]] + } }, - }, - ]; + params: { + networkId: 456, + } + }]; + 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); + }); + } - const nativeParamsWithSendTargetingKeys = [ - { - nativeParams: { - image: { - sendTargetingKeys: true + 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: {} }, - } - }, - { - nativeParams: { - icon: { - sendTargetingKeys: true + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] }, - } - }, - { - nativeParams: { - clickUrl: { - sendTargetingKeys: true + transactionId: 'transaction-123', + sizes: [[728, 90]], + params: { + zoneId: 123, + publisherSubId: '123' }, - } - }, - { - nativeParams: { - displayUrl: { - sendTargetingKeys: true + }, + { + bidder: 'criteo', + adUnitCode: 'bid-456', + mediaTypes: { + native: {} }, - } - }, - { - nativeParams: { - privacyLink: { - sendTargetingKeys: true + nativeOrtbRequest: { + assets: [{ + required: 1, + id: 1, + img: { + type: 3, + wmin: 100, + hmin: 100, + } + }] }, - } - }, - { - nativeParams: { - privacyIcon: { - sendTargetingKeys: true + transactionId: 'transaction-456', + sizes: [[728, 90]], + params: { + zoneId: 456, + publisherSubId: '456' }, + }, + ]; + + const nativeParamsWithSendTargetingKeys = [ + { + nativeParams: { + image: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + icon: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + clickUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + displayUrl: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyLink: { + sendTargetingKeys: true + }, + } + }, + { + nativeParams: { + privacyIcon: { + sendTargetingKeys: true + }, + } } - } - ]; + ]; - utilsMock.expects('logWarn') - .withArgs('Criteo: all native assets containing URL should be sent as placeholders with sendId(icon, image, clickUrl, displayUrl, privacyLink, privacyIcon)') - .exactly(nativeParamsWithSendTargetingKeys.length * bidRequests.length); - nativeParamsWithSendTargetingKeys.forEach(nativeParams => { - let transformedBidRequests = { ...bidRequests }; - transformedBidRequests = [Object.assign(transformedBidRequests[0], nativeParams), Object.assign(transformedBidRequests[1], nativeParams)]; - spec.buildRequests(transformedBidRequests, bidderRequest); + utilsMock.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 +2569,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 +2601,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 +2657,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); - - expect(bids).to.have.lengthOf(1); + }]; + 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) + }; - const theoreticalBidMetaPafData = { - paf: { - content_id: bidPafContentId, - transmission: pafTransmission - } - }; + const request = spec.buildRequests(bidRequests, syncAddFPDToBidderRequest(bidderRequest)); + const bids = spec.interpretResponse({ body: response }, request); - if (testCase.shouldContainsBidMetaPafData) { - expect(bids[0].meta).to.deep.equal(theoreticalBidMetaPafData); - } else { - expect(bids[0].meta).not.to.deep.equal(theoreticalBidMetaPafData); - } - }); - }); + expect(bids).to.have.lengthOf(1); - 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 +2725,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 +2768,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 +2778,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 +2873,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/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..e0f572c06ba 100644 --- a/test/spec/modules/dailymotionBidAdapter_spec.js +++ b/test/spec/modules/dailymotionBidAdapter_spec.js @@ -90,13 +90,1004 @@ 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: [], + 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 +1103,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 +1124,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 +1180,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 +1411,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 +1433,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 +1464,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 +1493,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 +1514,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 +1526,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 +1543,19 @@ 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: [], + videoViewsInSession: null, + autoplay: null, + playerName: 'dailymotion', + playerVolume: null, + }, }); }); @@ -336,7 +1579,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, }, }, }]; @@ -363,6 +1610,7 @@ describe('dailymotionBidAdapterTests', () => { 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 +1658,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 +1683,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 +1698,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 +1723,18 @@ 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.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 +1745,12 @@ describe('dailymotionBidAdapterTests', () => { }, }]; + config.setConfig({ + userSync: { + syncEnabled: false, + } + }); + const [request] = config.runWithBidder( 'dailymotion', () => spec.buildRequests(bidRequestDataWithApi, {}), @@ -470,6 +1763,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 +1791,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 +1815,17 @@ describe('dailymotionBidAdapterTests', () => { private: false, tags: '', title: '', + url: '', topics: '', - xid: '', livestream: false, + isCreatedForKids: null, + context: { + siteOrAppCat: [], + videoViewsInSession: null, + autoplay: null, + playerName: '', + playerVolume: null, + }, }); }); @@ -557,4 +1862,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/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..268e30f542d 100644 --- a/test/spec/modules/docereeAdManagerBidAdapter_spec.js +++ b/test/spec/modules/docereeAdManagerBidAdapter_spec.js @@ -1,5 +1,5 @@ import { expect } from 'chai'; -import { spec } from '../../../modules/docereeAdManagerBidAdapter.js'; +import { spec, getPayload } from '../../../modules/docereeAdManagerBidAdapter.js'; import { config } from '../../../src/config.js'; describe('docereeadmanager', function () { @@ -7,24 +7,25 @@ describe('docereeadmanager', function () { 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 +35,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 +124,90 @@ describe('docereeadmanager', function () { .empty; }); }); + + 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/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..2eee3b4356e 100644 --- a/test/spec/modules/e_volutionBidAdapter_spec.js +++ b/test/spec/modules/e_volutionBidAdapter_spec.js @@ -1,117 +1,301 @@ -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', + '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 +312,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -137,15 +322,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 +346,8 @@ describe('EvolutionTechBidAdapter', function () { currency: 'USD', dealId: '1', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -177,6 +364,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 +383,8 @@ describe('EvolutionTechBidAdapter', function () { netRevenue: true, currency: 'USD', meta: { - adomain: [ 'example.com' ] + advertiserDomains: ['google.com'], + advertiserId: 1234 } }] }; @@ -216,6 +405,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 +472,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..2162b844930 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 }; @@ -129,7 +153,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 +169,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 +244,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 +262,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/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..4468cb64f0c 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 () { @@ -130,7 +155,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 +171,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 +246,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 +264,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 +499,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/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/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/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..0920ca5bd78 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 }; @@ -129,7 +153,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 +169,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 +244,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 +262,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 +497,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..88062f2b785 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
' + @@ -188,7 +275,7 @@ describe('GPT pre-auction module', () => { customGptSlotMatching: false, customPbAdSlot: false, customPreAuction: false, - useDefaultPreAuction: false + useDefaultPreAuction: true }); }); }); @@ -454,4 +541,59 @@ describe('GPT pre-auction module', () => { expect(returnedAdUnits).to.deep.equal(expectedAdUnits); }); }); + + 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..4e13b1957b5 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); }); }); diff --git a/test/spec/modules/gumgumBidAdapter_spec.js b/test/spec/modules/gumgumBidAdapter_spec.js index 1c5940c06a3..e6fa6d468ca 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 () { diff --git a/test/spec/modules/hadronIdSystem_spec.js b/test/spec/modules/hadronIdSystem_spec.js index 85c8cc11c9e..899dc640dc1 100644 --- a/test/spec/modules/hadronIdSystem_spec.js +++ b/test/spec/modules/hadronIdSystem_spec.js @@ -1,6 +1,9 @@ import { hadronIdSubmodule, storage } from 'modules/hadronIdSystem.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'; describe('HadronIdSystem', function () { describe('getId', function() { @@ -52,4 +55,24 @@ describe('HadronIdSystem', function () { 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..140855194c5 100644 --- a/test/spec/modules/hadronRtdProvider_spec.js +++ b/test/spec/modules/hadronRtdProvider_spec.js @@ -1,7 +1,5 @@ -// 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'}; @@ -737,7 +735,7 @@ describe('hadronRtdProvider', function() { } }; - getDataFromLocalStorageStub.withArgs(HALOID_LOCAL_NAME).returns('testHadronId1'); + getDataFromLocalStorageStub.withArgs(HADRONID_LOCAL_NAME).returns('testHadronId1'); getRealTimeData(bidConfig, () => {}, rtdConfig, {}); let request = server.requests[0]; diff --git a/test/spec/modules/humansecurityRtdProvider_spec.js b/test/spec/modules/humansecurityRtdProvider_spec.js new file mode 100644 index 00000000000..8a0438c306f --- /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[1]).to.be.equal(SUBMODULE_NAME); + expect(args[2]).to.be.equal(onImplLoaded); + expect(args[3]).to.be.equal(null); + expect(args[4]).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..5dc94f544ea 100644 --- a/test/spec/modules/improvedigitalBidAdapter_spec.js +++ b/test/spec/modules/improvedigitalBidAdapter_spec.js @@ -10,7 +10,7 @@ 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'; @@ -32,6 +32,7 @@ describe('Improve Digital Adapter Tests', function () { const simpleBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 1053688 }, adUnitCode: 'div-gpt-ad-1499748733608-0', @@ -59,6 +60,7 @@ describe('Improve Digital Adapter Tests', function () { const instreamBidRequest = { bidder: 'improvedigital', params: { + publisherId: 1234, placementId: 123456 }, adUnitCode: 'video1', @@ -107,17 +109,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 +165,10 @@ describe('Improve Digital Adapter Tests', function () { return bidRequests; } + function formatPublisherUrl(baseUrl, publisherId) { + return `${baseUrl}/${publisherId}/${PB_ENDPOINT}`; + } + before(() => { hook.ready(); }); @@ -188,12 +183,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,19 +192,15 @@ 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 () { @@ -228,12 +214,10 @@ describe('Improve Digital Adapter Tests', function () { }); 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'); @@ -264,12 +248,10 @@ 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'); @@ -284,7 +266,6 @@ describe('Improve Digital Adapter Tests', function () { }, ...(FEATURES.VIDEO && { video: { - placement: OUTSTREAM_TYPE, w: 640, h: 480, mimes: ['video/mp4'], @@ -345,12 +326,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 = { @@ -405,8 +380,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 +449,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 +463,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 +505,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 +517,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 +535,7 @@ describe('Improve Digital Adapter Tests', function () { }); it('should add eids', function () { - const userIdAsEids = [ + const eids = [ { source: 'id5-sync.com', uids: [{ @@ -599,9 +551,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 +562,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 +574,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 +588,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 +596,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 +607,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 +632,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 +647,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 +672,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 +698,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 +755,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 +981,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"]},"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>  ', creativeId: '510265', dealId: 320896, netRevenue: false, @@ -1081,7 +991,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 +1004,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"]},"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>  ', 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/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..6c9a0fb9e79 --- /dev/null +++ b/test/spec/modules/intentIqAnalyticsAdapter_spec.js @@ -0,0 +1,247 @@ +import { expect } from 'chai'; +import iiqAnalyticsAnalyticsAdapter from 'modules/intentIqAnalyticsAdapter.js'; +import * as utils from 'src/utils.js'; +import * as detectBrowserUtils from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; +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 { FIRST_PARTY_KEY } from '../../../modules/intentIqIdSystem'; +import { REPORTER_ID, getReferrer, preparePayload } from '../../../modules/intentIqAnalyticsAdapter'; + +const partner = 10; +const defaultData = '{"pcid":"f961ffb1-a0e1-4696-a9d2-a21d815bd344", "group": "A"}'; +const version = 0.2; + +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, + manualReport: false + }; + 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); + + 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}&vrref=http://localhost:9876/`); + 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"}'); + + 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}&vrref=http://localhost:9876/`); + 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]`; + expect(request.url).to.equal( + `https://reports.intentiq.com/report?pid=${partner}&mct=1&iiqid=${defaultDataObj.pcid}&agid=${REPORTER_ID}&jsver=${version}&vrref=${getReferrer()}&source=pbjs&payload=${payload}` + ); + expect(dataToSend.pcid).to.equal(defaultDataObj.pcid) + }); + + it('should not send request if manualReport is true', function () { + iiqAnalyticsAnalyticsAdapter.initOptions.manualReport = true; + events.emit(EVENTS.BID_WON, wonRequest); + expect(server.requests.length).to.equal(0); + }); + + 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 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('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('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}&vrref=http://localhost:9876/`); + expect(request.url).to.contain('&payload='); + expect(request.url).to.contain('iiqid=f961ffb1-a0e1-4696-a9d2-a21d815bd344'); + }); +}); diff --git a/test/spec/modules/intentIqIdSystem_spec.js b/test/spec/modules/intentIqIdSystem_spec.js index ef174af416b..596185bedbd 100644 --- a/test/spec/modules/intentIqIdSystem_spec.js +++ b/test/spec/modules/intentIqIdSystem_spec.js @@ -2,30 +2,62 @@ 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 { CLIENT_HINTS_KEY, FIRST_PARTY_KEY, decryptData, handleClientHints, handleGPPData, readData } from '../../../modules/intentIqIdSystem'; +import { gppDataHandler, uspDataHandler } from '../../../src/consentHandler'; +import { clearAllCookies } from '../../helpers/cookies'; +import { detectBrowserFromUserAgent, detectBrowserFromUserAgentData } from '../../../libraries/detectBrowserUtils/detectBrowserUtils'; 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': 'U2FsdGVkX18AKRyhEPdiL9kuxSigBrlqLaDJWvwSird2O5TdBW67gX+xbL4nYxHDjdS5G5FpdhtpouZiBFw2FBjyUyobZheM857G5q4BapdiA8z3K6j0W+r0im30Ak2SSn2NBfFwxcCgP/UAF5/ddxIIaeWl1yBMZBO+Gic6us2JUg86paAtp3Sk4unCvg1G+4myYYSKgGi/Vrw51ye/jAGn4AdAbFOCojENhV+Ts/XyVK0AQGdC3wqnQUId9MZpB2VoTA9wgXeYEzjpDDJmcKQ18V3WTKnK/H1FBVZa1vovOj13ZUeuMUZbZL83NFE/PkCrzJjRy14orcdnGbDUaxXUBBulDCr21gNnc0mLbYj7b18OQQ75/GhX80HroxbMEyc5tiECBrE/JsW+2sQ4MwoqePPPj/f5Bf4wJ4z3UphjK6maypoWaXsZCZTp2mJYmsf0XsNHLpt1MUrBeAmy6Bewkb+WEAeVe6/b53DQDlo2LQXpSzDPVucMn3CQOWFv1Bvz5cLIZRD8/NtDjgYzWNXHRRAGhhN19yew0ZyeS09x3UBiwER6A6ppv2qQVGs8QNsif3z7pkhkNoETcXQKyv1xa5X87tLvXkO4FYDQQvXEGInyPuNmkFtsZS5FJl+TYrdyaEiCOwRgkshwCU4s93WrfRUtPHtd4zNiA1LWAKGKeBEK6woeFn1YU1YIqsvx9wXfkCbqNkHTi2mD1Eb85a2niSK9BzDdbxwv6EzZ+f9j6esEVdBUIiYmsUuOfTp/ftOHKjBKi1lbeC5imAzZfV/AKvqS5opAVGp7Y9pq976sYblCrPBQ0PYI+Cm2ZNhG1vKc2Pa0rjwJwvusZp2Wvw9zSbnoZUeBi1O+XGYqGhkqYVvH3rXvrFiSmA7pk5Buz6vPd6YV1d55PVahv/4u3jksEI/ZN8QNshrM0foJ4tE/q4x8EKx22txb6433QQybwFfExdmA/XaPqM0rwqTm4qyK0mbX984A8niQka5T5pPkEfL4ALqlIgJ2Fo7X/s6FRU/sZq72JWKcVET4edebD0w5mjeotsjUz5EGT0jRSWRba0yxe4myNaAyY7Y0NTNY9J9Q0JLDFh9Hb05Ejt0Jeoq4Olv8/zFWObBoQtkQyeeRB8L7XIari/xgl191J6euhe5+8vu3ta3tX+XGk+gqdfip1R11tEYpW/XPsV+6DBEfS/8icDHiwK7sPpAgTx7GuJGL1U3Hbg7P/2zUU6xMSR5In/Oa5i1B9FtayGd+utiqrGJsqg8IyFlAt1B9B11k/wJFnWWevMly+y+Ko75ShF7UzfcNR2s41doov+2DEz/YiKH1qHjVOXjslBTYjceB3xqa8sSPDt/vQDDUIX5CPLyVBZj7AeeB/IKDFjZVovBDH92Xl8JTNILRuDHsWmSwNI1DUzgus6ox4u9Mi439caK6KnpNYso+ksLXNEQCm0m15WV2NC+fjkEwLV6hGNbz' } let testResponseWithValues = { 'abPercentage': 90, @@ -39,11 +71,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 +102,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 +117,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 +129,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(['test_personid']); }); it('should call the IntentIQ endpoint with only partner', function () { @@ -111,8 +150,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 +236,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 () { @@ -217,21 +254,6 @@ describe('IntentIQ tests', function () { 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'); - }); - it('send addition parameters if were found in localstorage', function () { localStorage.setItem('_iiq_fdata_' + partner, JSON.stringify(testLSValue)) let callBackSpy = sinon.spy(); @@ -248,16 +270,226 @@ 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(JSON.stringify(returnedValue.id)).to.equal(decryptData(testLSValueWithData.data)); + }); + + 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('handleGPPData', function () { + it('should convert array of objects to a single JSON string', function () { + const input = [ + { key1: 'value1' }, + { key2: 'value2' } + ]; + const expectedOutput = JSON.stringify({ key1: 'value1', key2: 'value2' }); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should convert a single object to a JSON string', function () { + const input = { key1: 'value1', key2: 'value2' }; + const expectedOutput = JSON.stringify(input); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should handle empty object', function () { + const input = {}; + const expectedOutput = JSON.stringify(input); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + + it('should handle empty array', function () { + const input = []; + const expectedOutput = JSON.stringify({}); + const result = handleGPPData(input); + expect(result).to.equal(expectedOutput); + }); + }); + + 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 set cmpData.gpp and cmpData.gpp_sid if gppData exists and has parsedSections with usnat', function () { + const gppData = { + parsedSections: { + usnat: { key1: 'value1', key2: 'value2' } + }, + applicableSections: ['usnat'] + }; + gppDataHandlerStub.returns(gppData); + + 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; + + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.gpp_value).to.equal(JSON.stringify({ key1: 'value1', key2: 'value2' })); + }); + + it('should handle gppData without usnat in parsedSections', function () { + const gppData = { + parsedSections: { + euconsent: { key1: 'value1' } + }, + applicableSections: ['euconsent'] + }; + gppDataHandlerStub.returns(gppData); + + 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: true }) + ); + expect(callBackSpy.calledOnce).to.be.true; + + const firstPartyData = JSON.parse(localStorage.getItem(FIRST_PARTY_KEY)); + expect(firstPartyData.gpp_value).to.equal(''); + }); + }); + + 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)); }); }); 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..8f622cc39ed 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 }; @@ -129,7 +153,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 +169,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 +244,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 +262,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 () { @@ -370,6 +472,7 @@ describe('IQZoneBidAdapter', 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({}, {}, { @@ -394,5 +497,17 @@ describe('IQZoneBidAdapter', function () { 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') }); + 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.smartssp.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/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..e24c34dd1ab 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; } diff --git a/test/spec/modules/kimberliteBidAdapter_spec.js b/test/spec/modules/kimberliteBidAdapter_spec.js index 1480f1cc768..c0394a2090b 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 } 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,30 +128,44 @@ 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 = 'test vast'; beforeEach(function () { bidderResponse = { body: { id: requestId, seatbid: [{ - bid: [{ - crid: crId, - id: bidId, - impid: impId, - price: 1, - adm: adm - }] + bid: [ + { + crid: 1, + impid: 1, + price: 1, + adm: bannerAdm + }, + { + crid: 2, + impid: 2, + price: 1, + adm: videoAdm + } + ] }] } }; @@ -131,36 +175,64 @@ describe('kimberliteBidAdapter', function () { domain: 'example.com', page: 'https://www.example.com/test.html', }, - bids: [{ - bidId: impId, - mediaTypes: { - [BANNER]: {sizes: sizes} + bids: [ + { + bidId: 1, + mediaTypes: { + banner: {sizes: sizes} + }, + params: { + placementId: 'test-placement' + } }, - 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, + 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); }); it('pass on valid request', function () { const bids = spec.interpretResponse(bidderResponse, bidRequest); - assert.deepEqual(bids[0], expectedBid); + assert.deepEqual(bids[0], expectedBids[0]); + if (FEATURES.VIDEO) { + assert.deepEqual(bids[1], expectedBids[1]); + } }); it('fails on empty response', function () { 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..618648a0c07 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 () { @@ -122,7 +146,6 @@ describe('KiviAdsBidAdapter', function () { 'coppa', 'ccpa', 'gdpr', - 'gpp', 'tmax' ); expect(data.deviceWidth).to.be.a('number'); @@ -130,11 +153,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 +171,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 +246,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 +264,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 +499,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..452eb517eb8 100644 --- a/test/spec/modules/krushmediaBidAdapter_spec.js +++ b/test/spec/modules/krushmediaBidAdapter_spec.js @@ -1,147 +1,303 @@ -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', + '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 +312,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 +324,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 +346,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 +384,10 @@ describe('KrushmediabBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -306,6 +474,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 +484,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/liveIntentIdSystem_spec.js b/test/spec/modules/liveIntentIdSystem_spec.js index 373142db82e..dae19d0f578 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 * 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, @@ -363,29 +371,29 @@ describe('LiveIntentId', function() { 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 +403,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 +470,320 @@ 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 } + }); + }); + + 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}] + }); + }); + }) +}) 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..fbd4ba7c000 100644 --- a/test/spec/modules/lotamePanoramaIdSystem_spec.js +++ b/test/spec/modules/lotamePanoramaIdSystem_spec.js @@ -6,6 +6,8 @@ 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' }; @@ -21,7 +23,6 @@ describe('LotameId', function() { let requestHost; const nowTimestamp = new Date().getTime(); - beforeEach(function () { logErrorStub = sinon.stub(utils, 'logError'); getCookieStub = sinon.stub(storage, 'getCookie'); @@ -958,4 +959,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..f125eef4c5c 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 }; @@ -147,7 +169,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 +245,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 +262,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..f2653269c7b 100644 --- a/test/spec/modules/lunamediahbBidAdapter_spec.js +++ b/test/spec/modules/lunamediahbBidAdapter_spec.js @@ -1,145 +1,252 @@ 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', + '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 +261,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 +295,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 +307,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 +315,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 +333,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 +356,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 +448,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..df06a4e38f7 100644 --- a/test/spec/modules/magniteAnalyticsAdapter_spec.js +++ b/test/spec/modules/magniteAnalyticsAdapter_spec.js @@ -1758,16 +1758,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({ 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..e55e7175f4b 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 }; @@ -127,7 +153,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 +168,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 +195,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 +213,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 +423,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 +448,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..806f0adeabe 100644 --- a/test/spec/modules/mediaimpactBidAdapter_spec.js +++ b/test/spec/modules/mediaimpactBidAdapter_spec.js @@ -139,9 +139,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 +200,9 @@ describe('MediaimpactAdapter', function () { 'cpm': 0.01, 'currency': 'USD', 'netRevenue': true, - 'adomain': [ - 'test.domain' - ], + 'meta': { + 'advertiserDomains': ['test.domain'] + }, }; it('fill ad for response', function () { @@ -263,9 +263,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/medianetBidAdapter_spec.js b/test/spec/modules/medianetBidAdapter_spec.js index 4a221e97444..d3eab769f4a 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,7 +524,7 @@ 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, @@ -504,6 +602,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_NATIVE = { @@ -516,7 +615,7 @@ 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, @@ -595,6 +694,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD = { @@ -607,7 +707,7 @@ 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, @@ -684,6 +784,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_WITH_USERID = { @@ -696,7 +797,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' @@ -780,6 +881,114 @@ let VALID_BID_REQUEST = [{ } } }], + '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 + } + }, + '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': { + 'user': { + 'ext': { + 'eids': [{ + 'source': 'criteo.com', + 'uids': [{ + 'id': 'VZME3l9ycFFORncwaGJRVUNtUzB1UVhpWVd5TElrR3A5SHVSWXAwSFVPJTJCWiUyRnV2UTBPWjZOZ1ZrWnN4SldxcWUlMkJhUnFmUVNzUVg4N1NsdW84SGpUU1BsUllQSnN5bERMdFdPM2pWVXAlMkZVSSUyQkZsJTJGcktlenpZaHp0YXlvU25INWRQQ2tXciUyRk9PQmdac3RHeG9adDNKVzlRWE51ZyUzRCUzRA', + 'atype': 1 + } + ] + }] + } + }, + }, 'tmax': config.getConfig('bidderTimeout') }, VALID_PAYLOAD_WITH_CRID = { @@ -792,7 +1001,7 @@ 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, @@ -873,8 +1082,79 @@ 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 + }, + }, + '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 +1384,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 +1631,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, @@ -1310,6 +1710,7 @@ let VALID_BID_REQUEST = [{ } } }], + 'ortb2': {}, 'tmax': 3000, }, VALID_BIDDER_REQUEST_WITH_GPP_IN_ORTB2 = { @@ -1339,7 +1740,7 @@ 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, @@ -1547,9 +1948,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 +2140,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..4e603ef8448 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 () { @@ -160,6 +181,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 +276,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/minutemediaplusBidAdapter_spec.js b/test/spec/modules/minutemediaplusBidAdapter_spec.js deleted file mode 100644 index 5101f015b0e..00000000000 --- a/test/spec/modules/minutemediaplusBidAdapter_spec.js +++ /dev/null @@ -1,654 +0,0 @@ -import {expect} from 'chai'; -import { - spec as adapter, - createDomain, - hashCode, - extractPID, - extractCID, - extractSubDomain, - getStorageItem, - 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'; - -export const TEST_ID_SYSTEMS = ['britepoolid', 'criteoId', 'id5id', 'idl_env', 'lipb', 'netId', 'parrableId', 'pubcid', 'tdid', 'pubProvidedId']; - -const SUB_DOMAIN = 'exchange'; - -const BID = { - 'bidId': '2d52001cabd527', - 'adUnitCode': 'div-gpt-ad-12345-0', - 'params': { - 'subDomain': SUB_DOMAIN, - 'cId': '59db6b3b4ffaa70004f45cdc', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1, - 'ext': { - 'param1': 'loremipsum', - 'param2': 'dolorsitamet' - } - }, - 'placementCode': 'div-gpt-ad-1460505748561-0', - 'sizes': [[300, 250], [300, 600]], - 'bidderRequestId': '1fdb5ff1b6eaa7', - 'auctionId': 'auction_id', - 'bidRequestsCount': 4, - 'bidderRequestsCount': 3, - 'bidderWinsCount': 1, - 'requestId': 'b0777d85-d061-450e-9bc7-260dd54bbb7a', - 'schain': 'a0819c69-005b-41ed-af06-1be1e0aefefc', - 'mediaTypes': [BANNER], - 'ortb2Imp': { - 'ext': { - 'gpid': '1234567890', - tid: 'c881914b-a3b5-4ecf-ad9c-1c2f37c6aabf', - } - } -}; - -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, - 'cId': '635509f7ff6642d368cb9837', - 'pId': '59ac17c192832d0011283fe3', - 'bidFloor': 0.1 - }, - 'sizes': [[545, 307]], - 'mediaTypes': { - 'video': { - 'playerSize': [[545, 307]], - 'context': 'instream', - 'mimes': [ - 'video/mp4', - 'application/javascript' - ], - 'protocols': [2, 3, 5, 6], - 'maxduration': 60, - 'minduration': 0, - 'startdelay': 0, - 'linearity': 1, - 'api': [2], - 'placement': 1 - } - } -} - -const BIDDER_REQUEST = { - 'gdprConsent': { - 'consentString': 'consent_string', - 'gdprApplies': true - }, - 'gppString': 'gpp_string', - 'gppSid': [7], - 'uspConsent': 'consent_string', - 'refererInfo': { - 'page': 'https://www.greatsite.com', - 'ref': 'https://www.somereferrer.com' - }, - 'ortb2': { - 'regs': { - 'gpp': 'gpp_string', - 'gpp_sid': [7] - }, - 'device': { - 'sua': { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - } - } - }, -}; - -const SERVER_RESPONSE = { - body: { - cid: 'testcid123', - results: [{ - 'ad': '', - 'price': 0.8, - 'creativeId': '12610997325162499419', - 'exp': 30, - 'width': 300, - 'height': 250, - 'advertiserDomains': ['securepubads.g.doubleclick.net'], - 'cookies': [{ - 'src': 'https://sync.com', - 'type': 'iframe' - }, { - 'src': 'https://sync.com', - 'type': 'img' - }] - }] - } -}; - -const VIDEO_SERVER_RESPONSE = { - body: { - 'cid': '635509f7ff6642d368cb9837', - 'results': [{ - 'ad': '', - 'advertiserDomains': ['minutemedia-prebid.com'], - 'exp': 60, - 'width': 545, - 'height': 307, - 'mediaType': 'video', - 'creativeId': '12610997325162499419', - 'price': 2, - 'cookies': [] - }] - } -}; - -const REQUEST = { - data: { - width: 300, - height: 250, - bidId: '2d52001cabd527' - } -}; - -function getTopWindowQueryParams() { - try { - const parsedUrl = utils.parseUrl(window.top.document.URL, {decodeSearchAsString: true}); - return parsedUrl.search; - } catch (e) { - return ''; - } -} - -describe('MinuteMediaPlus Bid Adapter', function () { - describe('validtae spec', function () { - it('exists and is a function', function () { - expect(adapter.isBidRequestValid).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.buildRequests).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.interpretResponse).to.exist.and.to.be.a('function'); - }); - - it('exists and is a function', function () { - expect(adapter.getUserSyncs).to.exist.and.to.be.a('function'); - }); - - it('exists and is a string', function () { - expect(adapter.code).to.exist.and.to.be.a('string'); - }); - - it('exists and contains media types', function () { - expect(adapter.supportedMediaTypes).to.exist.and.to.be.an('array').with.length(2); - expect(adapter.supportedMediaTypes).to.contain.members([BANNER, VIDEO]); - }); - }); - - describe('validate bid requests', function () { - it('should require cId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - pId: 'pid' - } - }); - expect(isValid).to.be.false; - }); - - it('should require pId', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid' - } - }); - expect(isValid).to.be.false; - }); - - it('should validate correctly', function () { - const isValid = adapter.isBidRequestValid({ - params: { - cId: 'cid', - pId: 'pid' - } - }); - expect(isValid).to.be.true; - }); - }); - - describe('build requests', function () { - let sandbox; - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - sandbox = sinon.sandbox.create(); - sandbox.stub(Date, 'now').returns(1000); - }); - - it('should build video request', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000 - }); - const requests = adapter.buildRequests([VIDEO_BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/635509f7ff6642d368cb9837`, - data: { - adUnitCode: '63550ad1ff6642d368cba59dh5884270560', - bidFloor: 0.1, - bidId: '2d52001cabd527', - bidderVersion: adapter.version, - bidderRequestId: '12a8ae9ada9c13', - cb: 1000, - gdpr: 1, - gdprConsent: 'consent_string', - usPrivacy: 'consent_string', - gppString: 'gpp_string', - gppSid: [7], - prebidVersion: version, - transactionId: '56e184c6-bde9-497b-b9b9-cf47a61381ee', - auctionId: 'auction_id', - bidRequestsCount: 4, - bidderRequestsCount: 3, - bidderWinsCount: 1, - bidderTimeout: 3000, - publisherId: '59ac17c192832d0011283fe3', - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - res: `${window.top.screen.width}x${window.top.screen.height}`, - schain: VIDEO_BID.schain, - sizes: ['545x307'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - uqs: getTopWindowQueryParams(), - mediaTypes: { - video: { - api: [2], - context: 'instream', - linearity: 1, - maxduration: 60, - mimes: [ - 'video/mp4', - 'application/javascript' - ], - minduration: 0, - placement: 1, - playerSize: [[545, 307]], - protocols: [2, 3, 5, 6], - startdelay: 0 - } - }, - gpid: '' - } - }); - }); - - it('should build banner request for each size', function () { - const hashUrl = hashCode(BIDDER_REQUEST.refererInfo.page); - config.setConfig({ - bidderTimeout: 3000 - }); - const requests = adapter.buildRequests([BID], BIDDER_REQUEST); - expect(requests).to.have.length(1); - expect(requests[0]).to.deep.equal({ - method: 'POST', - url: `${createDomain(SUB_DOMAIN)}/prebid/multi/59db6b3b4ffaa70004f45cdc`, - data: { - gdprConsent: 'consent_string', - gdpr: 1, - 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', - sizes: ['300x250', '300x600'], - sua: { - 'source': 2, - 'platform': { - 'brand': 'Android', - 'version': ['8', '0', '0'] - }, - 'browsers': [ - {'brand': 'Not_A Brand', 'version': ['99', '0', '0', '0']}, - {'brand': 'Google Chrome', 'version': ['109', '0', '5414', '119']}, - {'brand': 'Chromium', 'version': ['109', '0', '5414', '119']} - ], - 'mobile': 1, - 'model': 'SM-G955U', - 'bitness': '64', - 'architecture': '' - }, - url: 'https%3A%2F%2Fwww.greatsite.com', - referrer: 'https://www.somereferrer.com', - cb: 1000, - bidFloor: 0.1, - bidId: '2d52001cabd527', - adUnitCode: 'div-gpt-ad-12345-0', - publisherId: '59ac17c192832d0011283fe3', - uniqueDealId: `${hashUrl}_${Date.now().toString()}`, - bidderVersion: adapter.version, - prebidVersion: version, - schain: BID.schain, - res: `${window.top.screen.width}x${window.top.screen.height}`, - mediaTypes: [BANNER], - gpid: '1234567890', - uqs: getTopWindowQueryParams(), - 'ext.param1': 'loremipsum', - 'ext.param2': 'dolorsitamet', - } - }); - }); - - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - sandbox.restore(); - }); - }); - describe('getUserSyncs', function () { - it('should have valid user sync with iframeEnabled', 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=' - }]); - }); - - it('should have valid user sync with cid on response', 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=' - }]); - }); - - it('should have valid user sync with pixelEnabled', 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=', - '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 () { - it('should return empty array when there is no response', function () { - const responses = adapter.interpretResponse(null); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no ad', function () { - const responses = adapter.interpretResponse({price: 1, ad: ''}); - expect(responses).to.be.empty; - }); - - it('should return empty array when there is no price', function () { - const responses = adapter.interpretResponse({price: null, ad: 'great ad'}); - expect(responses).to.be.empty; - }); - - it('should return an array of interpreted banner responses', function () { - const responses = adapter.interpretResponse(SERVER_RESPONSE, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 0.8, - width: 300, - height: 250, - creativeId: '12610997325162499419', - currency: 'USD', - netRevenue: true, - ttl: 30, - ad: '', - meta: { - advertiserDomains: ['securepubads.g.doubleclick.net'] - } - }); - }); - - it('should get meta from response metaData', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - serverResponse.body.results[0].metaData = { - advertiserDomains: ['minutemedia-prebid.com'], - agencyName: 'Agency Name', - }; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses[0].meta).to.deep.equal({ - advertiserDomains: ['minutemedia-prebid.com'], - agencyName: 'Agency Name' - }); - }); - - it('should return an array of interpreted video responses', function () { - const responses = adapter.interpretResponse(VIDEO_SERVER_RESPONSE, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0]).to.deep.equal({ - requestId: '2d52001cabd527', - cpm: 2, - width: 545, - height: 307, - mediaType: 'video', - creativeId: '12610997325162499419', - currency: 'USD', - netRevenue: true, - ttl: 60, - vastXml: '', - meta: { - advertiserDomains: ['minutemedia-prebid.com'] - } - }); - }); - - it('should take default TTL', function () { - const serverResponse = utils.deepClone(SERVER_RESPONSE); - delete serverResponse.body.results[0].exp; - const responses = adapter.interpretResponse(serverResponse, REQUEST); - expect(responses).to.have.length(1); - expect(responses[0].ttl).to.equal(300); - }); - }); - - describe('user id system', function () { - TEST_ID_SYSTEMS.forEach((idSystemProvider) => { - const id = Date.now().toString(); - const bid = utils.deepClone(BID); - - const userId = (function () { - switch (idSystemProvider) { - case 'lipb': - return {lipbid: id}; - case 'parrableId': - return {eid: id}; - case 'id5id': - return {uid: id}; - default: - return id; - } - })(); - - bid.userId = { - [idSystemProvider]: userId - }; - - it(`should include 'uid.${idSystemProvider}' in request params`, function () { - const requests = adapter.buildRequests([bid], BIDDER_REQUEST); - expect(requests[0].data[`uid.${idSystemProvider}`]).to.equal(id); - }); - }); - }); - - describe('alternate param names extractors', function () { - it('should return undefined when param not supported', function () { - const cid = extractCID({'c_id': '1'}); - const pid = extractPID({'p_id': '1'}); - const subDomain = extractSubDomain({'sub_domain': 'prebid'}); - expect(cid).to.be.undefined; - expect(pid).to.be.undefined; - expect(subDomain).to.be.undefined; - }); - - it('should return value when param supported', function () { - const cid = extractCID({'cID': '1'}); - const pid = extractPID({'Pid': '2'}); - const subDomain = extractSubDomain({'subDOMAIN': 'prebid'}); - expect(cid).to.be.equal('1'); - expect(pid).to.be.equal('2'); - expect(subDomain).to.be.equal('prebid'); - }); - }); - - describe('unique deal id', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - const key = 'myKey'; - let uniqueDealId; - beforeEach(() => { - uniqueDealId = getUniqueDealId(key, 0); - }) - - it('should get current unique deal id', function (done) { - // waiting some time so `now` will become past - setTimeout(() => { - const current = getUniqueDealId(key); - expect(current).to.be.equal(uniqueDealId); - done(); - }, 200); - }); - - it('should get new unique deal id on expiration', function (done) { - setTimeout(() => { - const current = getUniqueDealId(key, 100); - expect(current).to.not.be.equal(uniqueDealId); - done(); - }, 200) - }); - }); - - describe('storage utils', function () { - before(function () { - $$PREBID_GLOBAL$$.bidderSettings = { - mmplus: { - storageAllowed: true - } - }; - }); - after(function () { - $$PREBID_GLOBAL$$.bidderSettings = {}; - }); - it('should get value from storage with create param', function () { - const now = Date.now(); - const clock = useFakeTimers({ - shouldAdvanceTime: true, - now - }); - setStorageItem('myKey', 2020); - const {value, created} = getStorageItem('myKey'); - expect(created).to.be.equal(now); - expect(value).to.be.equal(2020); - expect(typeof value).to.be.equal('number'); - expect(typeof created).to.be.equal('number'); - clock.restore(); - }); - - it('should get external stored value', function () { - const value = 'superman' - window.localStorage.setItem('myExternalKey', value); - const item = getStorageItem('myExternalKey'); - expect(item).to.be.equal(value); - }); - - it('should parse JSON value', function () { - const data = JSON.stringify({event: 'send'}); - const {event} = tryParseJSON(data); - expect(event).to.be.equal('send'); - }); - - it('should get original value on parse fail', function () { - const value = 21; - const parsed = tryParseJSON(value); - expect(typeof parsed).to.be.equal('number'); - expect(parsed).to.be.equal(value); - }); - }); -}); diff --git a/test/spec/modules/mobfoxpbBidAdapter_spec.js b/test/spec/modules/mobfoxpbBidAdapter_spec.js index a4e58afbd1b..de289f8a5e5 100644 --- a/test/spec/modules/mobfoxpbBidAdapter_spec.js +++ b/test/spec/modules/mobfoxpbBidAdapter_spec.js @@ -1,148 +1,268 @@ -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', + '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 +272,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 +282,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 +311,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 +323,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 +345,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 +365,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 +383,10 @@ describe('MobfoxHBBidAdapter', function () { creativeId: '2', netRevenue: true, currency: 'USD', + meta: { + advertiserDomains: ['google.com'], + advertiserId: 1234 + } }] }; let nativeResponses = spec.interpretResponse(native); @@ -269,6 +406,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..cbe04b9f893 --- /dev/null +++ b/test/spec/modules/mobianRtdProvider_spec.js @@ -0,0 +1,186 @@ +import { expect } from 'chai'; +import sinon from 'sinon'; +import { mobianBrandSafetySubmodule, MOBIAN_URL } from 'modules/mobianRtdProvider.js'; +import * as ajax from 'src/ajax.js'; + +describe('Mobian RTD Submodule', function () { + let ajaxStub; + let bidReqConfig; + + beforeEach(function () { + bidReqConfig = { + ortb2Fragments: { + global: { + site: { + ext: { + data: {} + } + } + } + } + }; + }); + + afterEach(function () { + ajaxStub.restore(); + }); + + it('should set key-value pairs when server responds with valid data', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'low', + mobianSentiment: 'positive', + mobianContentCategories: [], + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ + risk: 'low', + contentCategories: [], + sentiment: 'positive', + emotions: ['joy'], + themes: [], + tones: [], + genres: [] + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'low', + mobianContentCategories: [], + mobianSentiment: 'positive', + mobianEmotions: ['joy'], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] + }); + }); + }); + + it('should handle response with content categories and multiple emotions', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'medium', + mobianSentiment: 'negative', + mobianContentCategories: ['arms', 'crime'], + mobianEmotions: ['anger', 'fear'], + mobianThemes: ['conflict', 'international relations'], + mobianTones: ['factual', 'serious'], + mobianGenres: ['news', 'political_analysis'] + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ + risk: 'medium', + contentCategories: ['arms', 'crime'], + sentiment: 'negative', + emotions: ['anger', 'fear'], + themes: ['conflict', 'international relations'], + tones: ['factual', 'serious'], + genres: ['news', 'political_analysis'] + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'medium', + mobianContentCategories: ['arms', 'crime'], + mobianSentiment: 'negative', + mobianEmotions: ['anger', 'fear'], + mobianThemes: ['conflict', 'international relations'], + mobianTones: ['factual', 'serious'], + mobianGenres: ['news', 'political_analysis'] + }); + }); + }); + + it('should return empty object when server responds with has_results: false', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: false + }, + results: {} + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({}); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.not.have.any.keys( + 'mobianRisk', 'mobianContentCategories', 'mobianSentiment', 'mobianEmotions', 'mobianThemes', 'mobianTones', 'mobianGenres' + ); + }); + }); + + it('should return empty object when server response is not valid JSON', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success('unexpected output not even of the right type'); + }); + const originalConfig = JSON.parse(JSON.stringify(bidReqConfig)); + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({}); + // Check that bidReqConfig hasn't been modified + expect(bidReqConfig).to.deep.equal(originalConfig); + }); + }); + + it('should handle error response', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.error(); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({}); + }); + }); + + it('should use default values when fields are missing in the response', function () { + ajaxStub = sinon.stub(ajax, 'ajaxBuilder').returns(function(url, callbacks) { + callbacks.success(JSON.stringify({ + meta: { + url: 'https://example.com', + has_results: true + }, + results: { + mobianRisk: 'high' + // Missing other fields + } + })); + }); + + return mobianBrandSafetySubmodule.getBidRequestData(bidReqConfig, {}, {}).then((result) => { + expect(result).to.deep.equal({ + risk: 'high', + contentCategories: [], + sentiment: 'unknown', + emotions: [], + themes: [], + tones: [], + genres: [], + }); + expect(bidReqConfig.ortb2Fragments.global.site.ext.data).to.deep.include({ + mobianRisk: 'high', + mobianContentCategories: [], + mobianSentiment: 'unknown', + mobianEmotions: [], + mobianThemes: [], + mobianTones: [], + mobianGenres: [] + }); + }); + }); +}); 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/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..01b09320055 100644 --- a/test/spec/modules/nextMillenniumBidAdapter_spec.js +++ b/test/spec/modules/nextMillenniumBidAdapter_spec.js @@ -15,6 +15,7 @@ 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', @@ -42,6 +43,7 @@ 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', @@ -74,6 +76,7 @@ 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', @@ -98,8 +101,8 @@ 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); }); } @@ -485,9 +488,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: undefined, - }, + }], }, expected: {}, @@ -497,9 +500,9 @@ describe('nextMillenniumBidAdapterTests', () => { title: 'setEids - userIdAsEids - array is empty', data: { postBody: {}, - bid: { + bids: [{ userIdAsEids: [], - }, + }], }, expected: {}, @@ -509,19 +512,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,8 +562,8 @@ 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); }); } @@ -606,110 +624,144 @@ describe('nextMillenniumBidAdapterTests', () => { } }; - 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 } }, - sizes: [[300, 250]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[1], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + banner: { + sizes: [[300, 250], [300, 300]] + } + }, - { - 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], [300, 300]], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - sizes: [[300, 250], [300, 300]], - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, + { + adUnitCode: adUnitCodes[2], + bidId: 'bid1234', + auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', + bidder: 'nextMillennium', + params: { group_id: '1234' }, + mediaTypes: { + video: { + playerSize: [640, 480], + } + }, - { - adUnitCode: 'test-video-gi', - bidId: 'bid1234', - auctionId: 'b06c5141-fe8f-4cdf-9d7d-54415490a917', - bidder: 'nextMillennium', - params: { group_id: '1234' }, - mediaTypes: { - video: { - playerSize: [640, 480], + uspConsent: '1---', + gdprConsent: { + consentString: 'kjfdniwjnifwenrif3', + gdprApplies: true } }, - - uspConsent: '1---', - gdprConsent: { - consentString: 'kjfdniwjnifwenrif3', - gdprApplies: true - } - }, - ]; + ]; + } 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; }); - it('use parameters group_id', function() { + it('check parameters group_id or placement_id', function() { 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; + 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(''); + } + + expect((/^[1-9]\d*[xX,][1-9]\d*$/).test(size)).to.be.true; + } + } else { + expect(srId.length).to.be.equal(1); + expect((/^[1-9]\d*/).test(srId[0])).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); + describe('Check ext.next_mil_imps', function() { + const expectedNextMilImps = [ + { + impId: 'nmi-test-0', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-1', + nextMillennium: {refresh_count: 1}, + }, + + { + impId: 'nmi-test-2', + nextMillennium: {refresh_count: 1}, + }, + ]; + + 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 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') - }) + const request = spec.buildRequests(bidRequestData); + expect(JSON.parse(request[0].data).site.domain).to.exist; + }); 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); @@ -806,11 +858,12 @@ describe('nextMillenniumBidAdapterTests', () => { expect(bid.currency).to.equal('USD'); }); - 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 +872,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 +883,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 +894,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 +908,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 +919,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 +934,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 7', eventName: 'bidResponse', - bid: { + bids: { bidderCode: 'appnexus', }, @@ -885,8 +944,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 +955,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 9', eventName: 'noBid', - bid: { + bids: { bidder: 'appnexus', }, @@ -904,8 +965,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 +976,9 @@ describe('nextMillenniumBidAdapterTests', () => { }, { + title: 'Check function of getting URL for sending statistics data - 11', eventName: 'bidTimeout', - bid: { + bids: { bidder: 'appnexus', }, @@ -923,19 +986,49 @@ 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 {eventName, bid, expected} of dataForTests) { - const url = spec._getUrlPixelMetric(eventName, bid); - expect(url).to.equal(expected); + for (let {title, eventName, bids, expected} of dataForTests) { + it(title, () => { + const url = spec._getUrlPixelMetric(eventName, bids); + expect(url).to.equal(expected); + }); }; - }) + }); }); 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..faaab727b17 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: { 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..ef04474c270 100644 --- a/test/spec/modules/optableBidAdapter_spec.js +++ b/test/spec/modules/optableBidAdapter_spec.js @@ -78,10 +78,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..47a7bf8779d --- /dev/null +++ b/test/spec/modules/orakiBidAdapter_spec.js @@ -0,0 +1,510 @@ +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', + '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..0f719a9d058 100644 --- a/test/spec/modules/paapi_spec.js +++ b/test/spec/modules/paapi_spec.js @@ -2,20 +2,28 @@ 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, + buyersToAuctionConfigs, getPAAPIConfig, + getPAAPISize, + IGB_TO_CONFIG, + mergeBuyers, + 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 +40,401 @@ 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); + events.emit(EVENTS.AUCTION_END, {auctionId}); + 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); + 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('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('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})); + }); + 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 }); }); - 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('removes configs from getPAAPIConfig if the module calls markAsUsed', () => { + submods[0].onAuctionConfig.callsFake((auctionId, configs, markAsUsed) => { + markAsUsed('au1'); }); - 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; - }) + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.eql({}); + }); + it('keeps them available if they do not', () => { + addPaapiConfigHook(nextFnSpy, {auctionId, adUnitCode: 'au1'}, paapiConfig); + events.emit(EVENTS.AUCTION_END, {auctionId, adUnitCodes: ['au1']}); + expect(getPAAPIConfig()).to.not.be.empty; }); }); + }); - describe('floor signal', () => { - before(() => { - if (!getGlobal().convertCurrency) { - getGlobal().convertCurrency = () => null; - getGlobal().convertCurrency.mock = true; - } - }); - after(() => { - if (getGlobal().convertCurrency.mock) { - delete getGlobal().convertCurrency; - } - }); + 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'); - }); + 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 +442,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}); + }); + + 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}); }); - after(function () { - Object.entries(navProps).forEach(([p, orig]) => navigator[p] = orig); + 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 +646,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 +664,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 +708,627 @@ describe('paapi module', () => { expectFledgeFlags({enabled: true, ae: 1}, {enabled: true, ae: 1}); }); - it('should not override pub-defined ext.ae', () => { + 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)); + }); + }); + }); + }); + describe('addPaapiData', () => { + function getEnrichedAdUnits() { + const next = sinon.stub(); + addPaapiData(next, adUnits); + sinon.assert.calledWith(next, adUnits); + return adUnits; + } + + 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])) + } + } + + 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 + }); + }); + + 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({ - bidderSequence: 'fixed', - [configNS]: { + paapi: { enabled: true, defaultForSlots: 1, } }); - Object.assign(adUnits[0], {ortb2Imp: {ext: {ae: 0}}}); - expectFledgeFlags({enabled: true, ae: 0}, {enabled: true, ae: 0}); }); + + 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, { + ...config.auctionConfig, + config: 0 + }); + sinon.assert.match(cf2, { + ...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.auctionSignals?.prebid).to.not.exist; + expect(cf2.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('ortb processors for fledge', () => { it('imp.ext.ae should be removed if fledge is not enabled', () => { - const imp = {ext: {ae: 1}}; + 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}}; - setImpExtAe(imp, {}, {bidderRequest: {fledgeEnabled: true}}); - expect(imp.ext.ae).to.equal(2); + 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('parseExtPrebidFledge', () => { - function packageConfigs(configs) { - return { - ext: { - prebid: { - fledge: { - auctionconfigs: configs - } - } - } - }; - } + describe('response parsing', () => { 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}})); - } - - function extractResult(ctx) { + 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/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..a90eadba8b0 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 }; @@ -147,7 +169,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 +245,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 +262,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..62ae2796596 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,6 +127,10 @@ 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'); @@ -143,6 +169,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 +202,10 @@ describe('PlaydigoBidAdapter', function () { }, params: { endpointId: 'testBanner', - } + }, + userIdAsEids } - ] + ]; let serverRequest = spec.buildRequests(bids, bidderRequest); @@ -190,6 +218,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..60cdd43504a 100644 --- a/test/spec/modules/precisoBidAdapter_spec.js +++ b/test/spec/modules/precisoBidAdapter_spec.js @@ -1,6 +1,6 @@ import { expect } from 'chai'; import { spec } from '../../../modules/precisoBidAdapter.js'; -import { config } from '../../../src/config.js'; +// simport { config } from '../../../src/config.js'; const DEFAULT_PRICE = 1 const DEFAULT_CURRENCY = 'USD' @@ -10,8 +10,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 +24,33 @@ describe('PrecisoAdapter', function () { sourceid: '0', publisherId: '0', mediaType: 'banner', - region: 'prebid-eu' + 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' + } + + } + }; describe('isBidRequestValid', function () { @@ -59,43 +79,17 @@ 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'); - }); - // 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); + 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'); }); - afterEach(function () { - config.getConfig.restore(); - }); - - 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('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; }); }); @@ -147,7 +141,8 @@ describe('PrecisoAdapter', function () { 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/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..aa880c206fa 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 }; @@ -129,7 +153,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 +169,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 +195,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 +213,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 +448,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..48f7a19e7d0 100644 --- a/test/spec/modules/pubmaticBidAdapter_spec.js +++ b/test/spec/modules/pubmaticBidAdapter_spec.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() { @@ -2946,7 +2948,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 +2961,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 +2974,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 () { @@ -3784,9 +3804,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'); }); }); @@ -4044,10 +4064,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 +4091,57 @@ 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); + }); + }); }); if (FEATURES.VIDEO) { diff --git a/test/spec/modules/pubriseBidAdapter_spec.js b/test/spec/modules/pubriseBidAdapter_spec.js new file mode 100644 index 00000000000..6374c4f1b7f --- /dev/null +++ b/test/spec/modules/pubriseBidAdapter_spec.js @@ -0,0 +1,514 @@ +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( + '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/qtBidAdapter_spec.js b/test/spec/modules/qtBidAdapter_spec.js new file mode 100644 index 00000000000..8bd8730c7a9 --- /dev/null +++ b/test/spec/modules/qtBidAdapter_spec.js @@ -0,0 +1,513 @@ +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', + '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/richaudienceBidAdapter_spec.js b/test/spec/modules/richaudienceBidAdapter_spec.js index d2b173f53df..782955103b3 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', @@ -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..f3f9ce8616f 100644 --- a/test/spec/modules/rtbhouseBidAdapter_spec.js +++ b/test/spec/modules/rtbhouseBidAdapter_spec.js @@ -43,12 +43,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 +460,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 +470,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 +480,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 +490,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 +500,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 +562,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 +825,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..2ce16ab8101 100644 --- a/test/spec/modules/rubiconBidAdapter_spec.js +++ b/test/spec/modules/rubiconBidAdapter_spec.js @@ -8,13 +8,12 @@ import { resetRubiConf, 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 +271,7 @@ describe('the rubicon adapter', function () { }], criteoId: '1111', }; - bid.userIdAsEids = [ + const eids = [ { 'source': 'liveintent.com', 'uids': [ @@ -347,6 +346,7 @@ describe('the rubicon adapter', function () { ] } ]; + bidderRequest.ortb2 = {user: {ext: {eids}}}; return bidderRequest; } @@ -515,7 +515,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 +547,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 +569,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 +608,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 +618,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 +634,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 +650,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 +661,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 +693,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 +703,9 @@ 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('ad engine query params should be ordered correctly', function () { @@ -741,15 +741,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 +757,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 +773,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 +795,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 +820,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 +849,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 +898,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 +918,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 +953,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 +986,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 +1080,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 +1164,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 +1219,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 +1260,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 () { @@ -1349,10 +1350,10 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(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'); + expect(data.get('tpid_tdid')).to.equal('abcd-efgh-ijkl-mnop-1234'); + expect(data.get('eid_adserver.org')).to.equal('abcd-efgh-ijkl-mnop-1234'); }); describe('LiveIntent support', function () { @@ -1382,11 +1383,11 @@ describe('the rubicon adapter', function () { } ]; let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(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'); + expect(data.get('tpid_liveintent.com')).to.equal('0000-1111-2222-3333'); + expect(data.get('eid_liveintent.com')).to.equal('0000-1111-2222-3333'); + expect(data.get('tg_v.LIseg')).to.equal('segA,segB'); }); it('should send tg_v.LIseg when userIdAsEids contains liveintentId with ext.segments as array', function () { @@ -1440,9 +1441,9 @@ describe('the rubicon adapter', function () { } ] let [request] = spec.buildRequests([clonedBid], bidderRequest); - let data = parseQuery(request.data); + let data = new URLSearchParams(request.data); - expect(data['x_liverampidl']).to.equal('1111-2222-3333-4444'); + expect(data.get('x_liverampidl')).to.equal('1111-2222-3333-4444'); }); }); @@ -1464,9 +1465,9 @@ describe('the rubicon adapter', function () { } ] 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'); }); }); @@ -1488,9 +1489,9 @@ describe('the rubicon adapter', function () { } ] 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'); }); }); @@ -1535,9 +1536,9 @@ describe('the rubicon adapter', function () { } ]; 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'); }); }); @@ -1567,9 +1568,9 @@ describe('the rubicon adapter', function () { } ]; 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^22222'); }); }); @@ -1585,9 +1586,9 @@ describe('the rubicon adapter', function () { }] }] 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 () { @@ -1601,9 +1602,9 @@ describe('the rubicon adapter', function () { }] }] 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'); }); }); @@ -1615,9 +1616,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 +1633,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 +1659,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 +1675,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 +1692,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 +1707,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 +1751,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 +1765,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 +1778,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 +1790,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 +1838,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 +1857,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 +1885,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 +1904,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 +1923,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 +1996,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 +2030,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; }); }); }); @@ -3695,14 +3696,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 () { @@ -3987,6 +3988,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); + }) }); } @@ -4215,7 +4226,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 +4449,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 +4460,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) => { 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..fe95ecb959c 100644 --- a/test/spec/modules/seedtagBidAdapter_spec.js +++ b/test/spec/modules/seedtagBidAdapter_spec.js @@ -38,6 +38,7 @@ function getSlotConfigs(mediaTypes, params) { ortb2Imp: { ext: { tid: 'd704d006-0d6e-4a09-ad6c-179e7e758096', + gpid: 'some-gpid' } }, adUnitCode: adUnitCode, @@ -300,6 +301,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 () { @@ -627,6 +629,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/sigmoidAnalyticsAdapter_spec.js b/test/spec/modules/sigmoidAnalyticsAdapter_spec.js deleted file mode 100644 index 1d8e38f19ec..00000000000 --- a/test/spec/modules/sigmoidAnalyticsAdapter_spec.js +++ /dev/null @@ -1,57 +0,0 @@ -import sigmoidAnalytic from 'modules/sigmoidAnalyticsAdapter.js'; -import {expect} from 'chai'; -import {expectEvents} from '../../helpers/analytics.js'; - -let events = require('src/events'); -let adapterManager = require('src/adapterManager').default; - -describe('sigmoid Prebid Analytic', function () { - after(function () { - sigmoidAnalytic.disableAnalytics(); - }); - - describe('enableAnalytics', function () { - beforeEach(function () { - sinon.spy(sigmoidAnalytic, 'track'); - sinon.stub(events, 'getEvents').returns([]); - }); - - afterEach(function () { - sigmoidAnalytic.track.restore(); - events.getEvents.restore(); - }); - it('should catch all events', function () { - adapterManager.registerAnalyticsAdapter({ - code: 'sigmoid', - adapter: sigmoidAnalytic - }); - - adapterManager.enableAnalytics({ - provider: 'sigmoid', - options: { - publisherIds: ['test_sigmoid_prebid_analytid_publisher_id'] - } - }); - - expectEvents().to.beTrackedBy(sigmoidAnalytic.track); - }); - }); - describe('build utm tag data', function () { - beforeEach(function () { - localStorage.setItem('sigmoid_analytics_utm_source', 'utm_source'); - localStorage.setItem('sigmoid_analytics_utm_medium', 'utm_medium'); - localStorage.setItem('sigmoid_analytics_utm_campaign', ''); - localStorage.setItem('sigmoid_analytics_utm_term', ''); - localStorage.setItem('sigmoid_analytics_utm_content', ''); - localStorage.setItem('sigmoid_analytics_utm_timeout', Date.now()); - }); - it('should build utm data from local storage', function () { - let utmTagData = sigmoidAnalytic.buildUtmTagData(); - expect(utmTagData.utm_source).to.equal('utm_source'); - expect(utmTagData.utm_medium).to.equal('utm_medium'); - expect(utmTagData.utm_campaign).to.equal(''); - expect(utmTagData.utm_term).to.equal(''); - expect(utmTagData.utm_content).to.equal(''); - }); - }); -}); diff --git a/test/spec/modules/silvermobBidAdapter_spec.js b/test/spec/modules/silvermobBidAdapter_spec.js index 7d7fbacc04e..3ff3dfbfe2d 100644 --- a/test/spec/modules/silvermobBidAdapter_spec.js +++ b/test/spec/modules/silvermobBidAdapter_spec.js @@ -11,7 +11,7 @@ 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'; diff --git a/test/spec/modules/sirdataRtdProvider_spec.js b/test/spec/modules/sirdataRtdProvider_spec.js index fbb5967bc20..9f6bb30e0b0 100644 --- a/test/spec/modules/sirdataRtdProvider_spec.js +++ b/test/spec/modules/sirdataRtdProvider_spec.js @@ -1,21 +1,123 @@ -import {addSegmentData, getSegmentsAndCategories, sirdataSubmodule, setOrtb2} from 'modules/sirdataRtdProvider.js'; +import { + addSegmentData, + getSegmentsAndCategories, + getUidFromStorage, + loadCustomFunction, + mergeEuidsArrays, + onDataDeletionRequest, + onDocumentReady, + postContentForSemanticAnalysis, + removePII, + sanitizeContent, + setOrtb2, + setUidInStorage, + sirdataSubmodule +} from 'modules/sirdataRtdProvider.js'; +import {expect} from 'chai'; +import {deepSetValue} from 'src/utils.js'; import {server} from 'test/mocks/xhr.js'; const responseHeader = {'Content-Type': 'application/json'}; describe('sirdataRtdProvider', function () { - describe('sirdataSubmodule', function () { + describe('sirdata Submodule init', function () { it('exists', function () { expect(sirdataSubmodule.init).to.be.a('function'); }); it('successfully instantiates', function () { - expect(sirdataSubmodule.init()).to.equal(true); + const moduleConfig = { + params: { + partnerId: 1, + key: 1, + } + }; + expect(sirdataSubmodule.init(moduleConfig)).to.equal(true); }); it('has the correct module name', function () { expect(sirdataSubmodule.name).to.equal('SirdataRTDModule'); }); }); + describe('Sanitize content', function () { + it('removes PII from content', function () { + let doc = document.implementation.createHTMLDocument(''); + let div = doc.createElement('div'); + div.className = 'test'; + div.setAttribute('test', 'test'); + div.textContent = 'My email is test@test.com, My bank account number is 123456789012, my SSN is 123-45-6789, and my credit card number is 1234 5678 9101 1121.Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'; + let div2 = doc.createElement('div'); + let div3 = doc.createElement('div'); + div3.innerText = 'hello'; + div2.appendChild(div3); + div.appendChild(div2); + doc.body.appendChild(div); + const cleanedDom = removePII(doc.documentElement.innerHTML); + const sanitizedDom = sanitizeContent(doc); + expect(cleanedDom).to.equal('
My email is , My bank account number is , my SSN is , and my credit card number is .Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
hello
'); + expect(sanitizedDom.documentElement.innerHTML).to.equal('
My email is , My bank account number is , my SSN is , and my credit card number is .Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.
hello
'); + }); + }); + + describe('setUidInStorage', function () { + it('sets Id in Storage', function () { + setUidInStorage('123456789'); + let val = getUidFromStorage(); + expect(val).to.deep.equal([{source: 'sddan.com', uids: [{id: '123456789', atype: 1}]}]); + }); + }); + + describe('mergeEuidsArrays', function () { + it('merges Euids Arrays', function () { + const object1 = [{source: 'sddan.com', uids: [{id: '123456789', atype: 1}]}]; + const object2 = [{source: 'sddan.com', uids: [{id: '987654321', atype: 1}]}]; + const object3 = mergeEuidsArrays(object1, object2); + expect(object3).to.deep.equal([{source: 'sddan.com', uids: [{id: '123456789', atype: 1}, {id: '987654321', atype: 1}]}]); + }); + }); + + describe('onDocumentReady', function () { + it('on Document Ready function execution', function () { + const testString = ''; + const testFunction = function() { return true; }; + let resString; + try { + resString = onDocumentReady(testString); + } catch (e) {} + expect(resString).to.be.false; + let resFunction = onDocumentReady(testFunction); + expect(resFunction).to.be.true; + }); + }); + + describe('postContentForSemanticAnalysis', function () { + it('gets content for analysis', function () { + let res = postContentForSemanticAnalysis('1223456', 'https://www.sirdata.com/'); + let resEmpty = postContentForSemanticAnalysis('1223456', ''); + expect(res).to.be.true; + expect(resEmpty).to.be.false; + }); + }); + + describe('loadCustomFunction', function () { + it('load function', function () { + const res = loadCustomFunction(function(...args) { return true; }, {}, {}, {}, {}); + expect(res).to.be.true; + }); + }); + + describe('onDataDeletionRequest', function () { + it('destroy id', function () { + const moduleConfig = { + params: { + partnerId: 1, + key: 1, + } + }; + const res = onDataDeletionRequest(moduleConfig); + expect(res).to.be.true; + }); + }); + describe('Add Segment Data', function () { it('adds segment data', function () { const firstConfig = { @@ -25,9 +127,12 @@ describe('sirdataRtdProvider', function () { setGptKeyValues: true, gptCurationId: 27449, contextualMinRelevancyScore: 50, + actualUrl: 'https://www.sirdata.com/', + cookieAccessGranted: true, bidders: [] } }; + sirdataSubmodule.init(firstConfig); let adUnits = [ { @@ -68,13 +173,12 @@ describe('sirdataRtdProvider', function () { 'segtaxid': 4, 'cattaxid': 7, 'contextual_categories': {'345': 100, '456': 100} - } + }, + 'sddan_id': '123456789', + 'post_content_token': '987654321' } - }; - - addSegmentData(firstReqBidsConfigObj, firstData, firstConfig, () => { - }); - + } + addSegmentData(firstReqBidsConfigObj, firstData, adUnits, function() { return true; }); expect(firstReqBidsConfigObj.ortb2Fragments.global.user.data[0].ext.segtax).to.equal(4); }); }); @@ -109,6 +213,7 @@ describe('sirdataRtdProvider', function () { }] } }; + sirdataSubmodule.init(config); let reqBidsConfigObj = { adUnits: [{ @@ -197,11 +302,13 @@ describe('sirdataRtdProvider', function () { 'cattaxid': 7, 'contextual_categories': {'345': 100, '456': 100} } - } + }, + 'sddan_id': '123456789', + 'post_content_token': '987654321' }; getSegmentsAndCategories(reqBidsConfigObj, () => { - }, config, {}); + }, {}, {}); let request = server.requests[0]; request.respond(200, responseHeader, JSON.stringify(data)); @@ -228,16 +335,6 @@ describe('sirdataRtdProvider', function () { describe('Set ortb2 for bidder', function () { it('set ortb2 for a givent bidder', function () { - const config = { - params: { - setGptKeyValues: false, - contextualMinRelevancyScore: 50, - bidders: [{ - bidder: 'appnexus', - }] - } - }; - let reqBidsConfigObj = { adUnits: [{ bids: [{ @@ -252,22 +349,6 @@ describe('sirdataRtdProvider', function () { } }; - let data = { - 'segments': [111111, 222222], - 'segtaxid': null, - 'cattaxid': null, - 'contextual_categories': {'333333': 100}, - 'shared_taxonomy': { - '27440': { - 'segments': [444444, 555555], - 'segtaxid': null, - 'cattaxid': null, - 'contextual_categories': {'666666': 100} - } - }, - 'global_taxonomy': {} - }; - window.googletag = window.googletag || {}; window.googletag.cmd = window.googletag.cmd || []; diff --git a/test/spec/modules/slimcutBidAdapter_spec.js b/test/spec/modules/slimcutBidAdapter_spec.js index da0fee48936..64ddac71899 100644 --- a/test/spec/modules/slimcutBidAdapter_spec.js +++ b/test/spec/modules/slimcutBidAdapter_spec.js @@ -35,26 +35,26 @@ describe('slimcutBidAdapter', function() { expect(spec.isBidRequestValid(bid)).to.equal(true); }); it('should return false when placementId is not valid (letters)', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': 'ABCD' }; - expect(spec.isBidRequestValid(bid)).to.equal(false); + expect(spec.isBidRequestValid(invalidBid)).to.equal(false); }); it('should return false when placementId < 0', function() { - let bid = Object.assign({}, bid); - delete bid.params; - bid.params = { + let invalidBid = Object.assign({}, bid); + delete invalidBid.params; + invalidBid.params = { 'placementId': -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; - 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/smaatoBidAdapter_spec.js b/test/spec/modules/smaatoBidAdapter_spec.js index 2ac2a1e5c33..302a5fa1aa6 100644 --- a/test/spec/modules/smaatoBidAdapter_spec.js +++ b/test/spec/modules/smaatoBidAdapter_spec.js @@ -8,10 +8,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 SYNC_URL = 'https://s.ad.smaato.net/c/?adExInit=p' + const ADTYPE_IMG = 'Img'; const ADTYPE_VIDEO = 'Video'; const ADTYPE_NATIVE = 'Native'; @@ -296,6 +298,33 @@ describe('smaatoBidAdapterTest', () => { expect(req.site.page).to.equal(page); expect(req.site.ref).to.equal(ref); expect(req.site.publisher.id).to.equal('publisherId'); + expect(req.dooh).to.be.undefined; + }) + + it('sends correct dooh from ortb2', () => { + const name = 'name'; + const domain = 'domain'; + const keywords = 'keyword1,keyword2'; + const venuetypetax = 1; + const ortb2 = { + dooh: { + name: name, + domain: domain, + keywords: keywords, + venuetypetax: venuetypetax + }, + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.dooh.id).to.exist.and.to.be.a('string'); + expect(req.dooh.name).to.equal(name); + expect(req.dooh.domain).to.equal(domain); + expect(req.dooh.keywords).to.equal(keywords); + expect(req.dooh.venuetypetax).to.equal(venuetypetax); + expect(req.dooh.publisher.id).to.equal('publisherId'); + expect(req.site).to.be.undefined; }) it('sends correct device from ortb2', () => { @@ -462,6 +491,48 @@ describe('smaatoBidAdapterTest', () => { const req = extractPayloadOfFirstAndOnlyRequest(reqs); expect(req.user.ext.eids).to.not.exist; }); + + it('sends dsa', () => { + const ortb2 = { + regs: { + ext: { + dsa: { + dsarequired: 2, + pubrender: 0, + datatopub: 1, + transparency: [ + { + domain: 'testdomain.com', + dsaparams: [1, 2, 3] + } + ] + } + } + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.dsa.dsarequired).to.eql(2); + expect(req.regs.ext.dsa.pubrender).to.eql(0); + expect(req.regs.ext.dsa.datatopub).to.eql(1); + expect(req.regs.ext.dsa.transparency[0].domain).to.eql('testdomain.com'); + expect(req.regs.ext.dsa.transparency[0].dsaparams).to.eql([1, 2, 3]); + }); + + it('sends no dsa', () => { + const ortb2 = { + regs: { + ext: {} + } + }; + + const reqs = spec.buildRequests([singleBannerBidRequest], {...defaultBidderRequest, ortb2}); + + const req = extractPayloadOfFirstAndOnlyRequest(reqs); + expect(req.regs.ext.dsa).to.be.undefined; + }); }); describe('multiple requests', () => { @@ -1566,11 +1637,73 @@ describe('smaatoBidAdapterTest', () => { expect(bids[0].netRevenue).to.equal(false); }); + + it('uses dsa object sent from server', () => { + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + const dsa = { + behalf: 'advertiser', + paid: 'advertiser', + adrender: 1, + transparency: [ + { + domain: 'dsp1domain.com', + dsaparams: [1, 2] + } + ] + }; + resp.body.seatbid[0].bid[0].ext.dsa = dsa; + + const bids = spec.interpretResponse(resp, buildBidRequest()); + + expect(bids[0].meta.dsa).to.deep.equal(dsa); + }); + + it('does not use dsa object if not sent from server', () => { + const resp = buildOpenRtbBidResponse(ADTYPE_IMG); + resp.body.seatbid[0].bid[0].ext = {} + + const bids = spec.interpretResponse(resp, buildBidRequest()); + + expect(bids[0].meta.dsa).to.be.undefined; + }); }); describe('getUserSyncs', () => { - it('returns no pixels', () => { + it('when pixelEnabled false then returns no pixels', () => { expect(spec.getUserSyncs()).to.be.empty }) + + it('when pixelEnabled true then returns pixel', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, null, null)).to.deep.equal( + [ + { + type: 'image', + url: SYNC_URL + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent then returns pixel with gdpr params', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, {gdprApplies: true, consentString: CONSENT_STRING}, null)).to.deep.equal( + [ + { + type: 'image', + url: `${SYNC_URL}&gdpr=1&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) + + it('when pixelEnabled true and gdprConsent without gdpr then returns pixel with gdpr_consent', () => { + expect(spec.getUserSyncs({pixelEnabled: true}, null, {consentString: CONSENT_STRING}, null), null).to.deep.equal( + [ + { + type: 'image', + url: `${SYNC_URL}&gdpr_consent=${CONSENT_STRING}` + } + ] + ) + }) }) }); diff --git a/test/spec/modules/smartadserverBidAdapter_spec.js b/test/spec/modules/smartadserverBidAdapter_spec.js index b01d95e2a4c..08b9f616551 100644 --- a/test/spec/modules/smartadserverBidAdapter_spec.js +++ b/test/spec/modules/smartadserverBidAdapter_spec.js @@ -458,6 +458,39 @@ describe('Smart bid adapter tests', function () { expect(syncs).to.have.lengthOf(0); }); + it('should set browsingTopics=false in request.options', () => { + const requests = spec.buildRequests(DEFAULT_PARAMS_WO_OPTIONAL); + expect(requests[0]).to.have.property('options').and.to.deep.equal({ + browsingTopics: false + }); + }); + + it('Verify metadata', function () { + const adomain = ['advertiser-domain.com']; + const dsa = { + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [{ + domain: 'smartadserver.com', + dsaparams: [1, 2] + }] + }; + const request = spec.buildRequests(DEFAULT_PARAMS); + const bids = spec.interpretResponse({ + body: { + ...BID_RESPONSE.body, + adomain, + dsa, + } + }, request[0]); + + expect(bids[0].cpm).to.equal(12); + expect(bids[0]).to.have.property('meta'); + expect(bids[0].meta).to.have.property('advertiserDomains').and.to.deep.equal(adomain); + expect(bids[0].meta).to.have.property('dsa').and.to.deep.equal(dsa); + }); + describe('gdpr tests', function () { afterEach(function () { config.setConfig({ ortb2: undefined }); @@ -1505,6 +1538,38 @@ describe('Smart bid adapter tests', function () { }); }); + describe('Digital Services Act (DSA)', function () { + it('should include dsa if ortb2.regs.ext.dsa available', function () { + const dsa = { + dsarequired: 1, + pubrender: 0, + datatopub: 1, + transparency: [ + { + domain: 'ok.domain.com', + dsaparams: [1, 2] + }, + { + domain: 'ko.domain.com', + dsaparams: [1, '3'] + } + ] + }; + + const bidRequests = deepClone(DEFAULT_PARAMS_WO_OPTIONAL); + bidRequests[0].ortb2 = { + regs: { + ext: { dsa } + } + }; + + const request = spec.buildRequests(bidRequests); + const requestContent = JSON.parse(request[0].data); + + expect(requestContent).to.have.property('dsa').and.to.deep.equal(dsa); + }); + }); + describe('#getValuableProperty method', function () { it('should return an object when calling with a number value', () => { const obj = spec.getValuableProperty('prop', 3); diff --git a/test/spec/modules/smarthubBidAdapter_spec.js b/test/spec/modules/smarthubBidAdapter_spec.js index e01d0c72f6b..eb5fe58093d 100644 --- a/test/spec/modules/smarthubBidAdapter_spec.js +++ b/test/spec/modules/smarthubBidAdapter_spec.js @@ -4,6 +4,7 @@ import { BANNER, VIDEO, NATIVE } from '../../../src/mediaTypes.js'; import { getUniqueIdentifierStr } from '../../../src/utils.js'; const bidder = 'smarthub' +const bidderAlias = 'markapp' describe('SmartHubBidAdapter', function () { const bids = [ @@ -24,6 +25,22 @@ describe('SmartHubBidAdapter', function () { pos: 1, } }, + { + bidId: getUniqueIdentifierStr(), + bidder: bidderAlias, + mediaTypes: { + [BANNER]: { + sizes: [[400, 350]] + } + }, + params: { + seat: 'testSeat', + token: 'testBanner', + iabCat: ['IAB1-1', 'IAB3-1', 'IAB4-3'], + minBidfloor: 9, + pos: 1, + } + }, { bidId: getUniqueIdentifierStr(), bidder: bidder, @@ -32,6 +49,7 @@ describe('SmartHubBidAdapter', function () { playerSize: [[300, 300]], minduration: 5, maxduration: 60, + plcmt: 1, } }, params: { @@ -88,7 +106,9 @@ describe('SmartHubBidAdapter', function () { const bidderRequest = { uspConsent: '1---', - gdprConsent: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw', + gdprConsent: { + consentString: 'COvFyGBOvFyGBAbAAAENAPCAAOAAAAAAAAAAAEEUACCKAAA.IFoEUQQgAIQwgIwQABAEAAAAOIAACAIAAAAQAIAgEAACEAAAAAgAQBAAAAAAAGBAAgAAAAAAAFAAECAAAgAAQARAEQAAAAAJAAIAAgAAAYQEAAAQmAgBC3ZAYzUw' + }, refererInfo: { page: 'https://test.com' }, @@ -105,7 +125,7 @@ describe('SmartHubBidAdapter', function () { }); describe('buildRequests', function () { - let [serverRequest] = spec.buildRequests(bids, bidderRequest); + let [serverRequest, requestAlias] = spec.buildRequests(bids, bidderRequest); it('Creates a ServerRequest object with method, URL and data', function () { expect(serverRequest).to.exist; @@ -119,7 +139,11 @@ describe('SmartHubBidAdapter', function () { }); it('Returns valid URL', function () { - expect(serverRequest.url).to.equal('https://testname-prebid.smart-hub.io/pbjs'); + expect(serverRequest.url).to.equal(`https://prebid.smart-hub.io/pbjs?partnerName=testname`); + }); + + it('Returns valid URL if alias', function () { + expect(requestAlias.url).to.equal(`https://${bidderAlias}-prebid.smart-hub.io/pbjs`); }); it('Returns general data valid', function () { @@ -144,7 +168,7 @@ describe('SmartHubBidAdapter', 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.consentString).to.be.a('string'); expect(data.ccpa).to.be.a('string'); expect(data.tmax).to.be.a('number'); expect(data.placements).to.have.lengthOf(3); @@ -176,6 +200,7 @@ describe('SmartHubBidAdapter', function () { expect(placement.playerSize).to.be.an('array'); expect(placement.minduration).to.be.an('number'); expect(placement.maxduration).to.be.an('number'); + expect(placement.plcmt).to.be.an('number'); break; case NATIVE: expect(placement.native).to.be.an('object'); @@ -189,8 +214,8 @@ describe('SmartHubBidAdapter', function () { serverRequest = spec.buildRequests(bids, bidderRequest); let data = serverRequest[0].data; expect(data.gdpr).to.exist; - expect(data.gdpr).to.be.a('string'); - expect(data.gdpr).to.equal(bidderRequest.gdprConsent); + expect(data.gdpr.consentString).to.be.a('string'); + expect(data.gdpr.consentString).to.equal(bidderRequest.gdprConsent.consentString); expect(data.ccpa).to.not.exist; delete bidderRequest.gdprConsent; }); diff --git a/test/spec/modules/smartxBidAdapter_spec.js b/test/spec/modules/smartxBidAdapter_spec.js index c3d0711632e..d8ddf7a398b 100644 --- a/test/spec/modules/smartxBidAdapter_spec.js +++ b/test/spec/modules/smartxBidAdapter_spec.js @@ -178,7 +178,6 @@ describe('The smartx adapter', function () { 2, 3, 5, 6 ], startdelay: 0, - placement: 1, pos: 1 }); @@ -208,10 +207,6 @@ describe('The smartx adapter', function () { sdk_name: 'Prebid 1+' }); - expect(request.data.imp[0].video).to.contain({ - placement: 1 - }); - bid.mediaTypes.video.context = 'outstream'; bid.params = { @@ -251,10 +246,6 @@ describe('The smartx adapter', function () { expect(request.data.imp[0].video.startdelay).to.equal(1); - expect(request.data.imp[0].video).to.contain({ - placement: 3 - }); - expect(request.data.imp[0].bidfloor).to.equal(55); expect(request.data.imp[0].bidfloorcur).to.equal('foo'); diff --git a/test/spec/modules/smartyadsAnalyticsAdapter_spec.js b/test/spec/modules/smartyadsAnalyticsAdapter_spec.js new file mode 100644 index 00000000000..de7e08a8a77 --- /dev/null +++ b/test/spec/modules/smartyadsAnalyticsAdapter_spec.js @@ -0,0 +1,441 @@ +import smartyadsAnalytics from 'modules/smartyadsAnalyticsAdapter.js'; +import { expect } from 'chai'; +import { server } from 'test/mocks/xhr.js'; +import { EVENTS } from '../../../src/constants'; + +let adapterManager = require('src/adapterManager').default; +let events = require('src/events'); + +describe('SmartyAds Analytics', function () { + const auctionEnd = { + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'timestamp': 1647424261187, + 'auctionEnd': 1647424261714, + 'auctionStatus': 'completed', + 'adUnits': [ + { + 'code': 'tag_200124_banner', + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'bids': [ + { + 'bidder': 'smartyads', + 'params': { + 'placementId': 123456 + } + }, + { + 'bidder': 'smartyadsAst', + 'params': { + 'placementId': 234567 + } + } + ], + 'sizes': [ + [ + 300, + 600 + ] + ], + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40' + } + ], + 'adUnitCodes': [ + 'tag_200124_banner' + ], + 'bidderRequests': [ + { + 'bidderCode': 'smartyads', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'bidderRequestId': '11dc6ff6378de7', + 'bids': [ + { + 'bidder': 'smartyads', + 'params': { + 'placementId': 123456 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '2bd3e8ff8a113f', + 'bidderRequestId': '11dc6ff6378de7', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0, + 'ova': 'cleared' + } + ], + 'auctionStart': 1647424261187, + 'timeout': 1000, + 'gdprConsent': { + 'consentString': 'CONSENT', + 'gdprApplies': true, + 'apiVersion': 2, + 'vendorData': 'a lot of borring stuff', + }, + 'start': 1647424261189 + }, + ], + 'noBids': [ + { + 'bidder': 'smartyadsAst', + 'params': { + 'placementId': 10471298 + }, + 'mediaTypes': { + 'banner': { + 'sizes': [ + [ + 300, + 600 + ] + ] + } + }, + 'adUnitCode': 'tag_200124_banner', + 'transactionId': 'de664ccb-e18b-4436-aeb0-362382eb1b40', + 'sizes': [ + [ + 300, + 600 + ] + ], + 'bidId': '5fe418f2d70364', + 'bidderRequestId': '4229a45ab8ea87', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'src': 'client', + 'bidRequestsCount': 1, + 'bidderRequestsCount': 1, + 'bidderWinsCount': 0 + } + ], + 'bidsReceived': [ + { + 'bidderCode': 'smartyads', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'video', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158534630', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ], + 'demandSource': 'something' + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261559, + 'requestTimestamp': 1647424261189, + 'bidder': 'smartyads', + 'adUnitCode': 'tag_200124_banner', + 'timeToRespond': 370, + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '300x600', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '300x600', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + } + } + ], + 'winningBids': [ + + ], + 'timeout': 1000 + }; + + let bidWon = { + 'bidderCode': 'smartyads', + 'width': 970, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '65d16ef039a97a', + 'requestId': '2bd3e8ff8a113f', + 'transactionId': '8b2a8629-d1ea-4bb1-aff0-e335b96dd002', + 'auctionId': '1e8b993d-8f0a-4232-83eb-3639ddf3a44b', + 'mediaType': 'banner', + 'source': 'client', + 'cpm': 27.4276, + 'creativeId': '158533702', + 'currency': 'USD', + 'netRevenue': true, + 'ttl': 2000, + 'ad': 'some html', + 'meta': { + 'advertiserDomains': [ + 'example.com' + ] + }, + 'renderer': 'something', + 'originalCpm': 25.02521, + 'originalCurrency': 'EUR', + 'responseTimestamp': 1647424261558, + 'requestTimestamp': 1647424261189, + 'bidder': 'smartyads', + 'adUnitCode': 'tag_200123_banner', + 'timeToRespond': 369, + 'originalBidder': 'smartyads', + 'pbLg': '5.00', + 'pbMg': '20.00', + 'pbHg': '20.00', + 'pbAg': '20.00', + 'pbDg': '20.00', + 'pbCg': '20.000000', + 'size': '970x250', + 'adserverTargeting': { + 'hb_bidder': 'smartyads', + 'hb_adid': '65d16ef039a97a', + 'hb_pb': '20.000000', + 'hb_size': '970x250', + 'hb_source': 'client', + 'hb_format': 'banner', + 'hb_adomain': 'example.com' + }, + 'status': 'rendered', + 'params': [ + { + 'placementId': 123456 + } + ] + }; + + let renderData = { + 'doc': { + 'location': { + 'ancestorOrigins': { + '0': 'http://localhost:9999' + }, + 'href': 'http://localhost:9999/integrationExamples/gpt/gdpr_hello_world.html?pbjs_debug=true', + 'origin': 'http://localhost:9999', + 'protocol': 'http:', + 'host': 'localhost:9999', + 'hostname': 'localhost', + 'port': '9999', + 'pathname': '/integrationExamples/gpt/gdpr_hello_world.html', + 'search': '?pbjs_debug=true', + 'hash': '' + }, + '__reactEvents$ffsiznmtn3p': {} + }, + 'bid': { + 'bidderCode': 'smartyads', + 'width': 300, + 'height': 250, + 'statusMessage': 'Bid available', + 'adId': '3c81d46b03abb', + 'requestId': '2be8997209a61c', + 'transactionId': 'b3091239-660a-4b13-94a1-1737e11743c5', + 'adUnitId': '8097f75e-8c3c-4b3e-aebb-b261caa5e331', + 'auctionId': '5c0d10bf-96cb-4afa-92ac-2ef75470da22', + 'mediaType': 'banner', + 'source': 'client', + 'ad': "
\"test
", + '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/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/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..b0bad74d59b --- /dev/null +++ b/test/spec/modules/ssp_genieeBidAdapter_spec.js @@ -0,0 +1,421 @@ +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(String(request[0].data.zoneid)).to.have.string( + `${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(String(request[0].data.loc)).to.have.string( + `${referer}` + ); + expect(String(request[0].data.referer)).to.have.string( + `${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(String(request[0].data.loc)).to.have.string( + `${encodeURIComponent(loc)}` + ); + expect(String(request[0].data.referer)).to.have.string( + `${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(String(request[0].data.ct0)).to.have.string(`${ct0}`); + }); + + it('should replaces currency with JPY if there is no currency provided', function () { + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.cur)).to.have.string('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(String(request[0].data.cur)).to.have.string('JPY'); + expect(String(request[1].data.cur)).to.have.string('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(String(request[0].data.ib)).to.have.string('0'); + expect(String(request[1].data.ib)).to.have.string('0'); + expect(String(request[2].data.ib)).to.have.string('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(String(request[0].data.adtk)).to.have.string('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(String(request[0].data.adtk)).to.have.string('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(String(request[0].data.idfa)).to.have.string(`${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(String(request[0].data.sw)).to.have.string(`${height}`); + expect(String(request[0].data.sh)).to.have.string(`${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(String(request[0].data.sw)).to.have.string(`${width}`); + expect(String(request[0].data.sh)).to.have.string(`${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(String(request[0].data.apid)).to.have.string(`${bundle}`); + }); + + it('should not include the extuid query when it does not contain the imuid cookie', function () { + const stub = sinon.stub(document, 'cookie').get(function () { + return ''; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(request[0].data).to.not.have.property('extuid'); + stub.restore(); + }); + + it('should include an extuid query when it contains an imuid cookie', function () { + const imuid = 'b.a4ad1d3eeb51e600'; + const stub = sinon.stub(document, 'cookie').get(function () { + return `_im_uid.3929=${imuid}`; + }); + const request = spec.buildRequests([BANNER_BID]); + expect(String(request[0].data.extuid)).to.have.string( + `${`im:${imuid}`}` + ); + stub.restore(); + }); + }); + }); + + 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.have.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..66e2b575b8b 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=', 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 new file mode 100644 index 00000000000..8289d02710a --- /dev/null +++ b/test/spec/modules/symitriDapRtdProvider_spec.js @@ -0,0 +1,661 @@ +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 +} from 'modules/symitriDapRtdProvider.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'}; + +let events = require('src/events'); + +describe('symitriDapRtdProvider', function() { + const testReqBidsConfigObj = { + adUnits: [ + { + bids: ['bid1', 'bid2'] + } + ] + }; + + const onDone = function() { return true }; + + const sampleGdprConsentConfig = { + 'gdpr': { + 'consentString': null, + 'vendorData': {}, + 'gdprApplies': true + } + }; + + const sampleUspConsentConfig = { + 'usp': '1YYY' + }; + + const sampleIdentity = { + 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 + } + } + + 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' + } + } + + const sampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 503, + 'identity': sampleIdentity + } + + const esampleConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 'x1', + 'domain': 'prebid.org', + 'segtax': 504, + '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 rtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [ + { + id: '1918' + }, + { + id: '1939' + } + ] + }; + + const encRtdUserObj = { + name: 'www.dataprovider3.com', + ext: { + segtax: 504, + taxonomyname: 'iab_audience_taxonomy' + }, + segment: [] + }; + + const cachedRtd = { + rtd: { + ortb2: { + user: { + data: [rtdUserObj] + } + } + } + }; + + let membership = { + said: cachedMembership.said, + cohorts: cachedMembership.cohorts, + attributes: null + }; + let encMembership = { + encryptedSegments: cachedEncryptedMembership.encryptedSegments + }; + encRtdUserObj.segment.push({ id: encMembership.encryptedSegments }); + const cachedEncRtd = { + rtd: { + ortb2: { + user: { + data: [encRtdUserObj] + } + } + } + }; + + before(() => { + hook.ready(); + }); + + let ortb2, bidConfig; + + beforeEach(function() { + bidConfig = {ortb2Fragments: {}}; + ortb2 = bidConfig.ortb2Fragments.global = {}; + config.resetConfig(); + storage.removeDataFromLocalStorage(DAP_TOKEN); + storage.removeDataFromLocalStorage(DAP_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_ENCRYPTED_MEMBERSHIP); + storage.removeDataFromLocalStorage(DAP_SS_ID); + }); + + afterEach(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') + try { + storage.setDataInLocalStorage(DAP_TOKEN, JSON.stringify(sampleCachedToken)); + expect(ortb2).to.eql({}); + generateRealTimeData(bidConfig, () => {}, emoduleConfig, {}); + + 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() + } + }); + }); + + 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 membershipRequest = server.requests[0]; + 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); + } 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) + 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' + 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); + } finally { + dapExtractExpiryFromTokenStub.restore(); + } + }); + }); + + 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 request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapTokenize success callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapTokenize(configAsync, sampleIdentity, onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapTokenize and dapMembership incorrect params', function () { + it('Onerror and config are null', function () { + expect(dapUtils.dapTokenize(null, 'identity', 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 + }; + const encConfig = { + 'api_hostname': 'prebid.dap.akadns.net', + 'api_version': 1, + 'domain': '', + 'segtax': 504 + }; + let identity = { + 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); + }); + }); + + 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); + }); + + it('dapGetMembershipFromLocalStorage success', function () { + 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)); + }); + }); + + 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 request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + it('dapMembership error callback', function () { + let configAsync = JSON.parse(JSON.stringify(sampleConfig)); + let submoduleCallback = dapUtils.dapMembership(configAsync, 'token', onDone, + function(token, status, xhr, onDone) { + }, + function(xhr, status, error, onDone) { + } + ); + let request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + 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 request = server.requests[0]; + request.respond(200, responseHeader, JSON.stringify('success')); + expect(submoduleCallback).to.equal(undefined); + }); + + 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 request = server.requests[0]; + request.respond(400, responseHeader, JSON.stringify('error')); + expect(submoduleCallback).to.equal(undefined); + }); + }); + + describe('dapMembership', function () { + it('should invoke the getDapToken and getDapMembership', function () { + let membership = { + said: 'item.said1', + cohorts: 'item.cohorts', + attributes: null + }; + + let getDapMembershipStub = sinon.stub(dapUtils, 'dapGetMembershipFromLocalStorage').returns(membership); + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); + try { + generateRealTimeData(testReqBidsConfigObj, onDone, cmoduleConfig); + expect(getDapMembershipStub.calledOnce).to.be.equal(true); + } finally { + getDapMembershipStub.restore(); + callDapApisStub.restore(); + } + }); + }); + + describe('dapEncMembership test', function () { + it('should invoke the getDapToken and getEncDapMembership', function () { + let encMembership = { + encryptedSegments: 'enc.seg', + }; + + let getDapEncMembershipStub = sinon.stub(dapUtils, 'dapGetEncryptedMembershipFromLocalStorage').returns(encMembership); + let callDapApisStub = sinon.stub(dapUtils, 'callDapAPIs'); + try { + generateRealTimeData(testReqBidsConfigObj, onDone, emoduleConfig); + expect(getDapEncMembershipStub.calledOnce).to.be.equal(true); + } finally { + getDapEncMembershipStub.restore(); + callDapApisStub.restore(); + } + }); + }); + + describe('dapGetRtdObj test', function () { + it('dapGetRtdObj', function () { + const config = { + apiHostname: 'prebid.dap.akadns.net', + apiVersion: 'x1', + domain: 'prebid.org', + segtax: 503 + }; + 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); + expect(ortb2.user.data).to.deep.include.members([encRtdUserObj]); + dapUtils.checkAndAddRealtimeData(ortb2, cachedRtd, 503); + 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); + }); + }); + + describe('dapRefreshToken test', function () { + it('test dapRefreshToken success response', function () { + 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); + }); + + it('test dapRefreshToken success response with deviceid 100', function () { + 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); + }); + + it('test dapRefreshToken success response with exp claim', function () { + 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' + 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); + }); + + it('test dapRefreshToken error response', function () { + 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 + }); + }); + + 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 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); + }); + + 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 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); + }); + + it('test dapRefreshEncryptedMembership error response', function () { + 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) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + let requestTokenize = server.requests[1]; + responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; + 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); + }); + }); + + 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 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); + }); + + 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 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); + }); + + it('test dapRefreshMembership 400 error response', function () { + 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) + let request = server.requests[0]; + request.respond(403, responseHeader, 'error'); + expect(server.requests.length).to.be.equal(DAP_MAX_RETRY_TOKENIZE); + }); + }); + + 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)); + }); + + 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); + }); + }); + + 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) + 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)); + }); + + 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) + let request = server.requests[0]; + let ssidHeader = request.requestHeaders['Symitri-DAP-SS-ID']; + responseHeader['Symitri-DAP-Token'] = sampleCachedToken.token; + request.respond(200, responseHeader, ''); + expect(ssidHeader).to.be.equal('Test_SSID_Spec'); + }); + }); + + 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); + }); + + 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); + }); + + it('USP consent present and user have opted out', function () { + 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); + }); + + it('USP consent present and user have not opted out', function () { + expect(symitriDapRtdSubmodule.init(null, {'usp': '1YNY'})).to.equal(true); + }); + }); + + describe('Test identifier is added properly to apiParams', function() { + let sandbox; + + beforeEach(() => { + sandbox = sinon.sandbox.create(); + }); + + afterEach(() => { + sandbox.restore(); + }); + + it('passed identifier is handled', async function () { + const test_identity = 'test_identity_1234'; + let identity = { + value: test_identity + }; + let apiParams = { + 'type': identity.type, + }; + + if (window.crypto && window.crypto.subtle) { + let hid = await dapUtils.addIdentifier(identity, apiParams).then(); + expect(hid['identity']).is.equal('843BE0FB20AAE699F27E5BC88C554B716F3DD366F58C1BDE0ACFB7EA0DD90CE7'); + } else { + expect(window.crypto.subtle).is.undefined + } + }); + + it('passed undefined identifier is handled', async function () { + const test_identity = undefined; + let identity = { + identity: test_identity + } + let apiParams = { + 'type': identity.type, + }; + + let hid = await dapUtils.addIdentifier(identity, apiParams); + expect(hid.identity).is.undefined; + }); + }); + + 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; + let adPixel = `${bidResponse.ad}" }); 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 +1309,19 @@ 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 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 +1331,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 +1363,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 +1396,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 +1445,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 +1839,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 +1912,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 +2520,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', @@ -3616,67 +3707,39 @@ 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); }); }); }); diff --git a/test/spec/unit/secureCreatives_spec.js b/test/spec/unit/secureCreatives_spec.js index 189066f7f88..64213073eee 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); - - 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); + 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); + 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,60 @@ 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; + }); }); + + 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 +482,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 +500,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..a6df236ee46 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 = { @@ -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